diff options
Diffstat (limited to 'components/script/dom')
72 files changed, 1842 insertions, 524 deletions
diff --git a/components/script/dom/bindings/codegen/Bindings.conf b/components/script/dom/bindings/codegen/Bindings.conf index d1d55d462bf..3a5f8576cef 100644 --- a/components/script/dom/bindings/codegen/Bindings.conf +++ b/components/script/dom/bindings/codegen/Bindings.conf @@ -146,6 +146,10 @@ DOMInterfaces = { 'GPU': { 'inCompartments': ['RequestAdapter'], +}, + +'GPUAdapter': { + 'inCompartments': ['RequestDevice'], } } diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 39e8bfa275d..ec29a59c9d4 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -3943,8 +3943,8 @@ class CGMemberJITInfo(CGThing): depth=self.descriptor.interface.inheritanceDepth(), opType=opType, 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 @@ -4131,8 +4131,8 @@ 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(): @@ -4202,8 +4202,8 @@ class CGMemberJITInfo(CGThing): if t.isUnion(): u = t.unroll() type = "JSJitInfo::Null as i32" if u.hasNullableType else "" - return reduce(CGMemberJITInfo.getSingleArgType, - u.flatMemberTypes, type) + return functools.reduce(CGMemberJITInfo.getSingleArgType, + u.flatMemberTypes, type) if t.isDictionary(): return "JSJitInfo_ArgType::Object as i32" if t.isDate(): @@ -5858,7 +5858,7 @@ class CGInterfaceTrait(CGThing): 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(): diff --git a/components/script/dom/bindings/codegen/Configuration.py b/components/script/dom/bindings/codegen/Configuration.py index 71c988e5378..84d8bd974aa 100644 --- a/components/script/dom/bindings/codegen/Configuration.py +++ b/components/script/dom/bindings/codegen/Configuration.py @@ -2,6 +2,7 @@ # License, v. 2.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 functools import os from WebIDL import IDLExternalInterface, IDLSequenceType, IDLWrapperType, WebIDLError @@ -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. @@ -62,7 +63,8 @@ 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)) + cmp = lambda x, y: (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] diff --git a/components/script/dom/bindings/error.rs b/components/script/dom/bindings/error.rs index b0b6ca1f685..dc03b660824 100644 --- a/components/script/dom/bindings/error.rs +++ b/components/script/dom/bindings/error.rs @@ -6,7 +6,6 @@ #[cfg(feature = "js_backtrace")] use crate::dom::bindings::cell::DomRefCell; -use crate::dom::bindings::codegen::Bindings::DOMExceptionBinding::DOMExceptionMethods; use crate::dom::bindings::codegen::PrototypeList::proto_id_to_name; use crate::dom::bindings::conversions::root_from_object; use crate::dom::bindings::conversions::{ @@ -221,7 +220,7 @@ impl ErrorInfo { Some(ErrorInfo { filename: "".to_string(), - message: exception.Stringifier().into(), + message: exception.stringifier().into(), lineno: 0, column: 0, }) diff --git a/components/script/dom/bindings/str.rs b/components/script/dom/bindings/str.rs index 38091fcf19e..2b4830dccfc 100644 --- a/components/script/dom/bindings/str.rs +++ b/components/script/dom/bindings/str.rs @@ -3,11 +3,11 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! The `ByteString` struct. - 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::borrow::{Borrow, Cow, ToOwned}; use std::default::Default; @@ -337,11 +337,11 @@ impl DOMString { /// https://html.spec.whatwg.org/multipage/#valid-floating-point-number pub fn is_valid_floating_point_number_string(&self) -> bool { - // for the case that `parse_floating_point_number` cannot handle - if self.0.contains(" ") { - return false; + lazy_static! { + static ref RE: Regex = + Regex::new(r"^-?(?:\d+\.\d+|\d+|\.\d+)(?:(e|E)(\+|\-)?\d+)?$").unwrap(); } - parse_floating_point_number(&self.0).is_ok() + RE.is_match(&self.0) && parse_floating_point_number(&self.0).is_ok() } /// https://html.spec.whatwg.org/multipage/#best-representation-of-the-number-as-a-floating-point-number diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index e865cf19e18..217e66837bb 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -37,9 +37,11 @@ use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::utils::WindowProxyHandler; use crate::dom::document::PendingRestyle; +use crate::dom::gpubuffer::GPUBufferState; use crate::dom::htmlimageelement::SourceSet; use crate::dom::htmlmediaelement::{HTMLMediaElementFetchContext, MediaFrameRenderer}; use crate::dom::identityhub::Identities; +use crate::script_runtime::StreamConsumer; use crate::task::TaskBox; use app_units::Au; use canvas_traits::canvas::{ @@ -60,7 +62,7 @@ use cssparser::RGBA; use devtools_traits::{CSSError, TimelineMarkerType, WorkerId}; use embedder_traits::{EventLoopWaker, MediaMetadata}; use encoding_rs::{Decoder, Encoding}; -use euclid::default::{Point2D, Rect, Rotation3D, Transform2D, Transform3D}; +use euclid::default::{Point2D, Rect, Rotation3D, Transform2D}; use euclid::Length as EuclidLength; use html5ever::buffer_queue::BufferQueue; use html5ever::{LocalName, Namespace, Prefix, QualName}; @@ -145,13 +147,15 @@ use style::values::specified::Length; use tendril::fmt::UTF8; use tendril::stream::LossyDecoder; use tendril::{StrTendril, TendrilSink}; -use time::{Duration, Timespec}; +use time::{Duration, Timespec, Tm}; use uuid::Uuid; -use webgpu::{WebGPU, WebGPUAdapter}; +use webgpu::{WebGPU, WebGPUAdapter, WebGPUBuffer, WebGPUDevice}; use webrender_api::{DocumentId, ImageKey}; use webvr_traits::{WebVRGamepadData, WebVRGamepadHand, WebVRGamepadState}; use webxr_api::SwapChainId as WebXRSwapChainId; +unsafe_no_jsmanaged_fields!(Tm); + /// A trait to allow tracing (only) DOM objects. pub unsafe trait JSTraceable { /// Trace `self`. @@ -508,9 +512,12 @@ unsafe_no_jsmanaged_fields!(WebGLTextureId); unsafe_no_jsmanaged_fields!(WebGLVertexArrayId); unsafe_no_jsmanaged_fields!(WebGLVersion); unsafe_no_jsmanaged_fields!(WebGLSLVersion); -unsafe_no_jsmanaged_fields!(WebGPU); unsafe_no_jsmanaged_fields!(RefCell<Identities>); +unsafe_no_jsmanaged_fields!(WebGPU); unsafe_no_jsmanaged_fields!(WebGPUAdapter); +unsafe_no_jsmanaged_fields!(WebGPUDevice); +unsafe_no_jsmanaged_fields!(WebGPUBuffer); +unsafe_no_jsmanaged_fields!(GPUBufferState); unsafe_no_jsmanaged_fields!(WebXRSwapChainId); unsafe_no_jsmanaged_fields!(MediaList); unsafe_no_jsmanaged_fields!(WebVRGamepadData, WebVRGamepadState, WebVRGamepadHand); @@ -536,7 +543,7 @@ 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>, Transform3D<f64>); +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); @@ -547,6 +554,7 @@ 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 impl<'a> JSTraceable for &'a str { #[inline] @@ -666,6 +674,20 @@ unsafe impl<T, U> JSTraceable for euclid::RigidTransform3D<f64, T, U> { } } +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 + } +} + unsafe impl<T> JSTraceable for EuclidLength<u64, T> { #[inline] unsafe fn trace(&self, _trc: *mut JSTracer) { diff --git a/components/script/dom/closeevent.rs b/components/script/dom/closeevent.rs index c88c3ac76aa..1c07813bce3 100644 --- a/components/script/dom/closeevent.rs +++ b/components/script/dom/closeevent.rs @@ -33,14 +33,6 @@ impl CloseEvent { } } - pub fn new_uninitialized(global: &GlobalScope) -> DomRoot<CloseEvent> { - reflect_dom_object( - Box::new(CloseEvent::new_inherited(false, 0, DOMString::new())), - global, - CloseEventBinding::Wrap, - ) - } - pub fn new( global: &GlobalScope, type_: Atom, diff --git a/components/script/dom/compositionevent.rs b/components/script/dom/compositionevent.rs index aa72db95d21..8b00ed1ac9c 100644 --- a/components/script/dom/compositionevent.rs +++ b/components/script/dom/compositionevent.rs @@ -21,6 +21,21 @@ pub struct CompositionEvent { } 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, + CompositionEventBinding::Wrap, + ) + } + pub fn new( window: &Window, type_: DOMString, diff --git a/components/script/dom/dedicatedworkerglobalscope.rs b/components/script/dom/dedicatedworkerglobalscope.rs index 829c0be6b4e..567d4fa6861 100644 --- a/components/script/dom/dedicatedworkerglobalscope.rs +++ b/components/script/dom/dedicatedworkerglobalscope.rs @@ -320,24 +320,20 @@ impl DedicatedWorkerGlobalScope { .credentials_mode(CredentialsMode::CredentialsSameOrigin) .parser_metadata(ParserMetadata::NotParserInserted) .use_url_credentials(true) - .pipeline_id(pipeline_id) + .pipeline_id(Some(pipeline_id)) .referrer(referrer) .referrer_policy(referrer_policy) .origin(origin); let runtime = unsafe { - if let Some(pipeline_id) = pipeline_id { - let task_source = NetworkingTaskSource( - Box::new(WorkerThreadWorkerChan { - sender: own_sender.clone(), - worker: worker.clone(), - }), - pipeline_id, - ); - new_child_runtime(parent, Some(task_source)) - } else { - new_child_runtime(parent, None) - } + let task_source = NetworkingTaskSource( + Box::new(WorkerThreadWorkerChan { + sender: own_sender.clone(), + worker: worker.clone(), + }), + pipeline_id, + ); + new_child_runtime(parent, Some(task_source)) }; let (devtools_mpsc_chan, devtools_mpsc_port) = unbounded(); @@ -375,7 +371,7 @@ impl DedicatedWorkerGlobalScope { .send(CommonScriptMsg::Task( WorkerEvent, Box::new(SimpleWorkerErrorHandler::new(worker)), - pipeline_id, + Some(pipeline_id), TaskSourceName::DOMManipulation, )) .unwrap(); @@ -476,9 +472,6 @@ impl DedicatedWorkerGlobalScope { 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) }, diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index f936dde16cb..94c9c8603c4 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -38,7 +38,6 @@ use crate::dom::bindings::xmlname::{ namespace_from_domstring, validate_and_extract, xml_name_type, }; use crate::dom::cdatasection::CDATASection; -use crate::dom::closeevent::CloseEvent; use crate::dom::comment::Comment; use crate::dom::compositionevent::CompositionEvent; use crate::dom::cssstylesheet::CSSStyleSheet; @@ -52,7 +51,6 @@ use crate::dom::element::CustomElementCreationMode; use crate::dom::element::{ Element, ElementCreator, ElementPerformFullscreenEnter, ElementPerformFullscreenExit, }; -use crate::dom::errorevent::ErrorEvent; use crate::dom::event::{Event, EventBubbles, EventCancelable, EventDefault, EventStatus}; use crate::dom::eventtarget::EventTarget; use crate::dom::focusevent::FocusEvent; @@ -81,9 +79,7 @@ use crate::dom::node::{LayoutNodeHelpers, Node, NodeDamage, NodeFlags, ShadowInc use crate::dom::nodeiterator::NodeIterator; use crate::dom::nodelist::NodeList; use crate::dom::pagetransitionevent::PageTransitionEvent; -use crate::dom::popstateevent::PopStateEvent; use crate::dom::processinginstruction::ProcessingInstruction; -use crate::dom::progressevent::ProgressEvent; use crate::dom::promise::Promise; use crate::dom::range::Range; use crate::dom::servoparser::ServoParser; @@ -97,7 +93,6 @@ use crate::dom::touchlist::TouchList; use crate::dom::treewalker::TreeWalker; use crate::dom::uievent::UIEvent; use crate::dom::virtualmethods::vtable_for; -use crate::dom::webglcontextevent::WebGLContextEvent; use crate::dom::webglrenderingcontext::WebGLRenderingContext; use crate::dom::wheelevent::WheelEvent; use crate::dom::window::{ReflowReason, Window}; @@ -3814,15 +3809,14 @@ impl DocumentMethods for Document { "beforeunloadevent" => Ok(DomRoot::upcast(BeforeUnloadEvent::new_uninitialized( &self.window, ))), - "closeevent" => Ok(DomRoot::upcast(CloseEvent::new_uninitialized( - self.window.upcast(), - ))), + "compositionevent" | "textevent" => Ok(DomRoot::upcast( + CompositionEvent::new_uninitialized(&self.window), + )), "customevent" => Ok(DomRoot::upcast(CustomEvent::new_uninitialized( self.window.upcast(), ))), - "errorevent" => Ok(DomRoot::upcast(ErrorEvent::new_uninitialized( - self.window.upcast(), - ))), + // FIXME(#25136): devicemotionevent, deviceorientationevent + // FIXME(#7529): dragevent "events" | "event" | "htmlevents" | "svgevents" => { Ok(Event::new_uninitialized(&self.window.upcast())) }, @@ -3839,15 +3833,6 @@ impl DocumentMethods for Document { "mouseevent" | "mouseevents" => { Ok(DomRoot::upcast(MouseEvent::new_uninitialized(&self.window))) }, - "pagetransitionevent" => Ok(DomRoot::upcast(PageTransitionEvent::new_uninitialized( - &self.window, - ))), - "popstateevent" => Ok(DomRoot::upcast(PopStateEvent::new_uninitialized( - &self.window, - ))), - "progressevent" => Ok(DomRoot::upcast(ProgressEvent::new_uninitialized( - self.window.upcast(), - ))), "storageevent" => Ok(DomRoot::upcast(StorageEvent::new_uninitialized( &self.window, "".into(), @@ -3859,9 +3844,6 @@ impl DocumentMethods for Document { &TouchList::new(&self.window, &[]), ))), "uievent" | "uievents" => Ok(DomRoot::upcast(UIEvent::new_uninitialized(&self.window))), - "webglcontextevent" => Ok(DomRoot::upcast(WebGLContextEvent::new_uninitialized( - &self.window, - ))), _ => Err(Error::NotSupported), } } diff --git a/components/script/dom/domexception.rs b/components/script/dom/domexception.rs index 3cfe6092dd1..218771851b6 100644 --- a/components/script/dom/domexception.rs +++ b/components/script/dom/domexception.rs @@ -149,6 +149,11 @@ impl DOMException { DOMExceptionBinding::Wrap, )) } + + // not an IDL stringifier, used internally + pub fn stringifier(&self) -> DOMString { + DOMString::from(format!("{}: {}", self.name, self.message)) + } } impl DOMExceptionMethods for DOMException { @@ -169,9 +174,4 @@ impl DOMExceptionMethods for DOMException { fn Message(&self) -> DOMString { self.message.clone() } - - // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-error.prototype.tostring - fn Stringifier(&self) -> DOMString { - DOMString::from(format!("{}: {}", self.name, self.message)) - } } diff --git a/components/script/dom/domtokenlist.rs b/components/script/dom/domtokenlist.rs index d6c5ac1537f..b870ab4f16d 100644 --- a/components/script/dom/domtokenlist.rs +++ b/components/script/dom/domtokenlist.rs @@ -151,7 +151,7 @@ impl DOMTokenListMethods for DOMTokenList { } // 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); @@ -164,6 +164,7 @@ 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; @@ -173,8 +174,9 @@ impl DOMTokenListMethods for DOMTokenList { // Step 5. self.element .set_atomic_tokenlist_attribute(&self.local_name, atoms); + result = true; } - Ok(()) + Ok(result) } // check-tidy: no specs after this line diff --git a/components/script/dom/eventtarget.rs b/components/script/dom/eventtarget.rs index 12cf7d0253b..ea259a6d2d7 100644 --- a/components/script/dom/eventtarget.rs +++ b/components/script/dom/eventtarget.rs @@ -227,14 +227,13 @@ impl CompiledEventListener { 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(); } } diff --git a/components/script/dom/fakexrdevice.rs b/components/script/dom/fakexrdevice.rs index d76c94751c0..bb73580e98a 100644 --- a/components/script/dom/fakexrdevice.rs +++ b/components/script/dom/fakexrdevice.rs @@ -20,7 +20,7 @@ use ipc_channel::ipc::IpcSender; use ipc_channel::router::ROUTER; use profile_traits::ipc; use std::rc::Rc; -use webxr_api::{MockDeviceMsg, View, Views}; +use webxr_api::{MockDeviceMsg, MockViewInit, MockViewsInit}; #[dom_struct] pub struct FakeXRDevice { @@ -50,58 +50,57 @@ impl FakeXRDevice { } } -pub fn get_views(views: &[FakeXRViewInit]) -> Fallible<Views> { - if views.len() != 2 { - return Err(Error::NotSupported); - } - - 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), - }; - - if left.projectionMatrix.len() != 16 || - right.projectionMatrix.len() != 16 || - left.viewOffset.position.len() != 3 || - right.viewOffset.position.len() != 3 - { +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_l = [0.; 16]; - let mut proj_r = [0.; 16]; - let v: Vec<_> = left.projectionMatrix.iter().map(|x| **x).collect(); - proj_l.copy_from_slice(&v); - let proj_l = Transform3D::from_array(proj_l); - let v: Vec<_> = right.projectionMatrix.iter().map(|x| **x).collect(); - proj_r.copy_from_slice(&v); - let proj_r = Transform3D::from_array(proj_r); + 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 offset_l = get_origin(&left.viewOffset)?.inverse(); - let offset_r = get_origin(&right.viewOffset)?.inverse(); - - let size_l = Size2D::new(views[0].resolution.width, views[0].resolution.height); - let size_r = Size2D::new(views[1].resolution.width, views[1].resolution.height); + let transform = get_origin(&view.viewOffset)?.inverse(); - let origin_l = Point2D::new(0, 0); - let origin_r = Point2D::new(size_l.width, 0); - - let viewport_l = Rect::new(origin_l, size_l); - let viewport_r = Rect::new(origin_r, size_r); - - let left = View { - projection: proj_l, - transform: offset_l, - viewport: viewport_l, + 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 right = View { - projection: proj_r, - transform: offset_r, - viewport: viewport_r, + 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(Views::Stereo(left, right)) + + 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>( @@ -134,7 +133,7 @@ impl FakeXRDeviceMethods for FakeXRDevice { Ok(()) } - /// https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-setviewerorigin fn SetViewerOrigin( &self, origin: &FakeXRRigidTransformInit, @@ -142,7 +141,25 @@ impl FakeXRDeviceMethods for FakeXRDevice { ) -> Fallible<()> { let _ = self .sender - .send(MockDeviceMsg::SetViewerOrigin(get_origin(origin)?)); + .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(()) } diff --git a/components/script/dom/filereader.rs b/components/script/dom/filereader.rs index 95b0572c3c5..d40ae95aac3 100644 --- a/components/script/dom/filereader.rs +++ b/components/script/dom/filereader.rs @@ -23,8 +23,7 @@ use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::progressevent::ProgressEvent; use crate::script_runtime::JSContext; -use crate::task::TaskCanceller; -use crate::task_source::file_reading::{FileReadingTask, FileReadingTaskSource}; +use crate::task_source::file_reading::FileReadingTask; use crate::task_source::{TaskSource, TaskSourceName}; use base64; use dom_struct::dom_struct; @@ -37,8 +36,6 @@ use mime::{self, Mime}; use servo_atoms::Atom; use std::cell::Cell; use std::ptr; -use std::sync::Arc; -use std::thread; #[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] pub enum FileReaderFunction { @@ -236,7 +233,7 @@ impl FileReader { filereader: TrustedFileReader, gen_id: GenerationId, data: ReadMetaData, - blob_contents: Arc<Vec<u8>>, + blob_contents: Vec<u8>, ) { let fr = filereader.root(); @@ -426,6 +423,7 @@ impl FileReader { self.generation_id.set(GenerationId(prev_id + 1)); } + /// <https://w3c.github.io/FileAPI/#readOperation> fn read( &self, function: FileReaderFunction, @@ -443,35 +441,40 @@ impl FileReader { // Step 3 *self.result.borrow_mut() = None; - let blob_contents = Arc::new(blob.get_bytes().unwrap_or(vec![])); - 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 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, - canceller, - ) - }) - .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(()) } @@ -480,25 +483,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, - canceller: TaskCanceller, -) { - // Step 4 - 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, data, blob_contents); - task_source.queue_with_canceller(task, &canceller).unwrap(); -} diff --git a/components/script/dom/formdata.rs b/components/script/dom/formdata.rs index d8df761cea7..b8503a4f0dc 100644 --- a/components/script/dom/formdata.rs +++ b/components/script/dom/formdata.rs @@ -56,7 +56,7 @@ impl FormData { form: Option<&HTMLFormElement>, ) -> Fallible<DomRoot<FormData>> { if let Some(opt_form) = form { - return match opt_form.get_form_dataset(None) { + return match opt_form.get_form_dataset(None, None) { Some(form_datums) => Ok(FormData::new(Some(form_datums), global)), None => Err(Error::InvalidState), }; diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index e6b367a9858..f0748433e64 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -48,7 +48,7 @@ use crate::task_source::TaskSourceName; use crate::timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle}; use crate::timers::{OneshotTimers, TimerCallback}; use content_security_policy::CspList; -use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId}; +use devtools_traits::{PageError, ScriptToDevtoolsControlMsg}; use dom_struct::dom_struct; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; @@ -102,7 +102,6 @@ impl Drop for AutoCloseWorker { pub struct GlobalScope { eventtarget: EventTarget, crypto: MutNullableDom<Crypto>, - next_worker_id: Cell<WorkerId>, /// The message-port router id for this global, if it is managing ports. message_port_state: DomRefCell<MessagePortState>, @@ -389,7 +388,6 @@ impl GlobalScope { blob_state: DomRefCell::new(BlobState::UnManaged), eventtarget: EventTarget::new_inherited(), crypto: Default::default(), - next_worker_id: Cell::new(WorkerId(0)), pipeline_id, devtools_wants_updates: Default::default(), console_timers: DomRefCell::new(Default::default()), @@ -1368,14 +1366,6 @@ impl GlobalScope { self.crypto.or_init(|| Crypto::new(self)) } - /// 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 live_devtools_updates(&self) -> bool { self.devtools_wants_updates.get() } @@ -1412,6 +1402,29 @@ 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) -> &profile_mem::ProfilerChan { &self.mem_profiler_chan @@ -1527,6 +1540,27 @@ impl GlobalScope { // 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, + }, + )); + } } } } diff --git a/components/script/dom/gpu.rs b/components/script/dom/gpu.rs index 4e47620052e..8a3e05f2d79 100644 --- a/components/script/dom/gpu.rs +++ b/components/script/dom/gpu.rs @@ -117,11 +117,13 @@ impl GPUMethods for GPU { let promise = Promise::new_in_current_compartment(&self.global(), comp); let sender = response_async(&promise, self); let power_preference = match options.powerPreference { - Some(GPUPowerPreference::Low_power) => wgpu::PowerPreference::LowPower, - Some(GPUPowerPreference::High_performance) => wgpu::PowerPreference::HighPerformance, - None => wgpu::PowerPreference::Default, + Some(GPUPowerPreference::Low_power) => wgpu::instance::PowerPreference::LowPower, + Some(GPUPowerPreference::High_performance) => { + wgpu::instance::PowerPreference::HighPerformance + }, + None => wgpu::instance::PowerPreference::Default, }; - let id = self.global().as_window().Navigator().create_adapter_id(); + let ids = self.global().as_window().Navigator().create_adapter_ids(); match self.wgpu_channel() { Some(channel) => { @@ -129,8 +131,8 @@ impl GPUMethods for GPU { .0 .send(WebGPURequest::RequestAdapter( sender, - wgpu::RequestAdapterOptions { power_preference }, - id, + wgpu::instance::RequestAdapterOptions { power_preference }, + ids, )) .unwrap(); }, @@ -146,7 +148,7 @@ impl AsyncWGPUListener for GPU { WebGPUResponse::RequestAdapter(name, adapter) => { let adapter = GPUAdapter::new( &self.global(), - DOMString::from(name), + DOMString::from(format!("{} ({:?})", name, adapter.0.backend())), Heap::default(), adapter, ); diff --git a/components/script/dom/gpuadapter.rs b/components/script/dom/gpuadapter.rs index ec970439e8a..cbb24917ebe 100644 --- a/components/script/dom/gpuadapter.rs +++ b/components/script/dom/gpuadapter.rs @@ -2,16 +2,28 @@ * License, v. 2.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::{self, GPUAdapterMethods}; -use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::compartments::InCompartment; +use crate::dom::bindings::codegen::Bindings::GPUAdapterBinding::{ + self, GPUAdapterMethods, GPUDeviceDescriptor, +}; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +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::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::dom::window::Window; use crate::script_runtime::JSContext as SafeJSContext; use dom_struct::dom_struct; use js::jsapi::{Heap, JSObject}; use std::ptr::NonNull; -use webgpu::WebGPUAdapter; +use std::rc::Rc; +use webgpu::{wgpu, WebGPUAdapter, WebGPURequest, WebGPUResponse}; #[dom_struct] pub struct GPUAdapter { @@ -60,4 +72,53 @@ impl GPUAdapterMethods for GPUAdapter { 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: InCompartment) -> Rc<Promise> { + let promise = Promise::new_in_current_compartment(&self.global(), comp); + let sender = response_async(&promise, self); + let desc = wgpu::instance::DeviceDescriptor { + extensions: wgpu::instance::Extensions { + anisotropic_filtering: descriptor.extensions.anisotropicFiltering, + }, + limits: wgpu::instance::Limits { + max_bind_groups: descriptor.limits.maxBindGroups, + }, + }; + if let Some(window) = self.global().downcast::<Window>() { + let id = window + .Navigator() + .create_device_id(self.adapter.0.backend()); + match window.webgpu_channel() { + Some(thread) => thread + .0 + .send(WebGPURequest::RequestDevice(sender, self.adapter, desc, id)) + .unwrap(), + None => promise.reject_error(Error::Type("No WebGPU thread...".to_owned())), + } + } else { + promise.reject_error(Error::Type("No WebGPU thread...".to_owned())) + }; + promise + } +} + +impl AsyncWGPUListener for GPUAdapter { + fn handle_response(&self, response: WebGPUResponse, promise: &Rc<Promise>) { + match response { + WebGPUResponse::RequestDevice(device_id, _descriptor) => { + let device = GPUDevice::new( + &self.global(), + &self, + Heap::default(), + Heap::default(), + device_id, + ); + promise.resolve_native(&device); + }, + _ => promise.reject_error(Error::Type( + "Wrong response type from WebGPU thread...".to_owned(), + )), + } + } } diff --git a/components/script/dom/gpubuffer.rs b/components/script/dom/gpubuffer.rs new file mode 100644 index 00000000000..a2a3e3b62eb --- /dev/null +++ b/components/script/dom/gpubuffer.rs @@ -0,0 +1,120 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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::{ + self, GPUBufferMethods, GPUBufferSize, +}; +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 ipc_channel::ipc::IpcSender; +use std::cell::Cell; +use webgpu::{WebGPUBuffer, WebGPUDevice, WebGPURequest}; + +#[derive(MallocSizeOf)] +pub enum GPUBufferState { + Mapped, + Unmapped, + Destroyed, +} + +#[dom_struct] +pub struct GPUBuffer { + reflector_: Reflector, + #[ignore_malloc_size_of = "channels are hard"] + channel: IpcSender<WebGPURequest>, + label: DomRefCell<Option<DOMString>>, + size: GPUBufferSize, + usage: u32, + state: DomRefCell<GPUBufferState>, + buffer: WebGPUBuffer, + device: WebGPUDevice, + valid: Cell<bool>, +} + +impl GPUBuffer { + fn new_inherited( + channel: IpcSender<WebGPURequest>, + buffer: WebGPUBuffer, + device: WebGPUDevice, + state: GPUBufferState, + size: GPUBufferSize, + usage: u32, + valid: bool, + ) -> GPUBuffer { + Self { + reflector_: Reflector::new(), + channel, + label: DomRefCell::new(None), + state: DomRefCell::new(state), + size: size, + usage: usage, + valid: Cell::new(valid), + device, + buffer, + } + } + + #[allow(unsafe_code)] + pub fn new( + global: &GlobalScope, + channel: IpcSender<WebGPURequest>, + buffer: WebGPUBuffer, + device: WebGPUDevice, + state: GPUBufferState, + size: GPUBufferSize, + usage: u32, + valid: bool, + ) -> DomRoot<GPUBuffer> { + reflect_dom_object( + Box::new(GPUBuffer::new_inherited( + channel, buffer, device, state, size, usage, valid, + )), + global, + GPUBufferBinding::Wrap, + ) + } +} + +impl Drop for GPUBuffer { + fn drop(&mut self) { + self.Destroy() + } +} + +impl GPUBufferMethods for GPUBuffer { + /// https://gpuweb.github.io/gpuweb/#dom-gpubuffer-unmap + fn Unmap(&self) { + self.channel + .send(WebGPURequest::UnmapBuffer(self.buffer)) + .unwrap(); + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpubuffer-destroy + fn Destroy(&self) { + match *self.state.borrow() { + GPUBufferState::Mapped => { + self.Unmap(); + }, + _ => {}, + }; + self.channel + .send(WebGPURequest::DestroyBuffer(self.buffer)) + .unwrap(); + *self.state.borrow_mut() = GPUBufferState::Destroyed; + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<DOMString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<DOMString>) { + *self.label.borrow_mut() = value; + } +} 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/gpudevice.rs b/components/script/dom/gpudevice.rs new file mode 100644 index 00000000000..81974735b55 --- /dev/null +++ b/components/script/dom/gpudevice.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/. */ + +#![allow(unsafe_code)] + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPUBufferBinding::GPUBufferDescriptor; +use crate::dom::bindings::codegen::Bindings::GPUDeviceBinding::{self, GPUDeviceMethods}; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +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::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpuadapter::GPUAdapter; +use crate::dom::gpubuffer::{GPUBuffer, GPUBufferState}; +use crate::dom::window::Window; +use crate::script_runtime::JSContext as SafeJSContext; +use dom_struct::dom_struct; +use ipc_channel::ipc; +use js::jsapi::{Heap, JSObject}; +use js::jsval::{JSVal, ObjectValue, UndefinedValue}; +use js::typedarray::{ArrayBuffer, CreateWith}; +use std::ptr::{self, NonNull}; +use webgpu::wgpu::resource::{BufferDescriptor, BufferUsage}; +use webgpu::{WebGPUBuffer, WebGPUDevice, WebGPURequest}; + +#[dom_struct] +pub struct GPUDevice { + eventtarget: EventTarget, + adapter: Dom<GPUAdapter>, + #[ignore_malloc_size_of = "mozjs"] + extensions: Heap<*mut JSObject>, + #[ignore_malloc_size_of = "mozjs"] + limits: Heap<*mut JSObject>, + label: DomRefCell<Option<DOMString>>, + device: WebGPUDevice, +} + +impl GPUDevice { + fn new_inherited( + adapter: &GPUAdapter, + extensions: Heap<*mut JSObject>, + limits: Heap<*mut JSObject>, + device: WebGPUDevice, + ) -> GPUDevice { + Self { + eventtarget: EventTarget::new_inherited(), + adapter: Dom::from_ref(adapter), + extensions, + limits, + label: DomRefCell::new(None), + device, + } + } + + #[allow(unsafe_code)] + pub fn new( + global: &GlobalScope, + adapter: &GPUAdapter, + extensions: Heap<*mut JSObject>, + limits: Heap<*mut JSObject>, + device: WebGPUDevice, + ) -> DomRoot<GPUDevice> { + reflect_dom_object( + Box::new(GPUDevice::new_inherited( + adapter, extensions, limits, device, + )), + global, + GPUDeviceBinding::Wrap, + ) + } +} + +impl GPUDevice { + unsafe fn resolve_create_buffer_mapped( + &self, + cx: SafeJSContext, + channel: ipc_channel::ipc::IpcSender<WebGPURequest>, + gpu_buffer: WebGPUBuffer, + array_buffer: Vec<u8>, + descriptor: BufferDescriptor, + valid: bool, + ) -> Vec<JSVal> { + rooted!(in(*cx) let mut js_array_buffer = ptr::null_mut::<JSObject>()); + let mut out = Vec::new(); + assert!(ArrayBuffer::create( + *cx, + CreateWith::Slice(array_buffer.as_slice()), + js_array_buffer.handle_mut(), + ) + .is_ok()); + + let buff = GPUBuffer::new( + &self.global(), + channel, + gpu_buffer, + self.device, + GPUBufferState::Mapped, + descriptor.size, + descriptor.usage.bits(), + valid, + ); + out.push(ObjectValue(buff.reflector().get_jsobject().get())); + out.push(ObjectValue(js_array_buffer.get())); + out + } + + fn validate_buffer_descriptor( + &self, + descriptor: &GPUBufferDescriptor, + ) -> (bool, BufferDescriptor) { + // TODO: Record a validation error in the current scope if the descriptor is invalid. + let wgpu_usage = BufferUsage::from_bits(descriptor.usage); + let valid = wgpu_usage.is_some() && descriptor.size > 0; + + if valid { + ( + true, + BufferDescriptor { + size: descriptor.size, + usage: wgpu_usage.unwrap(), + }, + ) + } else { + ( + false, + BufferDescriptor { + size: 0, + usage: BufferUsage::STORAGE, + }, + ) + } + } +} + +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> { + NonNull::new(self.extensions.get()).unwrap() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<DOMString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<DOMString>) { + *self.label.borrow_mut() = value; + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-createbuffer + fn CreateBuffer(&self, descriptor: &GPUBufferDescriptor) -> DomRoot<GPUBuffer> { + let (valid, wgpu_descriptor) = self.validate_buffer_descriptor(descriptor); + let channel; + let (sender, receiver) = ipc::channel().unwrap(); + if let Some(window) = self.global().downcast::<Window>() { + let id = window.Navigator().create_buffer_id(self.device.0.backend()); + match window.webgpu_channel() { + Some(thread) => { + channel = thread.0.clone(); + thread + .0 + .send(WebGPURequest::CreateBuffer( + sender, + self.device, + id, + wgpu_descriptor, + )) + .unwrap(); + }, + None => unimplemented!(), + } + } else { + unimplemented!() + }; + + let buffer = receiver.recv().unwrap(); + + GPUBuffer::new( + &self.global(), + channel, + buffer, + self.device, + GPUBufferState::Unmapped, + descriptor.size, + descriptor.usage, + valid, + ) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-createbuffermapped + fn CreateBufferMapped( + &self, + cx: SafeJSContext, + descriptor: &GPUBufferDescriptor, + ) -> Vec<JSVal> { + let (valid, wgpu_descriptor) = self.validate_buffer_descriptor(descriptor); + let channel; + let (sender, receiver) = ipc::channel().unwrap(); + rooted!(in(*cx) let js_val = UndefinedValue()); + if let Some(window) = self.global().downcast::<Window>() { + let id = window.Navigator().create_buffer_id(self.device.0.backend()); + match window.webgpu_channel() { + Some(thread) => { + channel = thread.0.clone(); + thread + .0 + .send(WebGPURequest::CreateBufferMapped( + sender, + self.device, + id, + wgpu_descriptor.clone(), + )) + .unwrap() + }, + None => return vec![js_val.get()], + } + } else { + return vec![js_val.get()]; + }; + + let (buffer, array_buffer) = receiver.recv().unwrap(); + + unsafe { + self.resolve_create_buffer_mapped( + cx, + channel, + buffer, + array_buffer, + wgpu_descriptor, + valid, + ) + } + } +} diff --git a/components/script/dom/headers.rs b/components/script/dom/headers.rs index 017cde9268a..61c6fc1f98e 100644 --- a/components/script/dom/headers.rs +++ b/components/script/dom/headers.rs @@ -14,9 +14,8 @@ use crate::dom::bindings::str::{is_token, ByteString}; use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; use http::header::{self, HeaderMap as HyperHeaders, HeaderName, HeaderValue}; -use mime::{self, Mime}; +use net_traits::request::is_cors_safelisted_request_header; use std::cell::Cell; -use std::result::Result; use std::str::{self, FromStr}; #[dom_struct] @@ -28,7 +27,7 @@ pub struct Headers { } // https://fetch.spec.whatwg.org/#concept-headers-guard -#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] pub enum Guard { Immutable, Request, @@ -88,6 +87,9 @@ 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 @@ -301,35 +303,6 @@ 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.type_(), value_mime.subtype()) { - (mime::APPLICATION, mime::WWW_FORM_URLENCODED) | - (mime::MULTIPART, mime::FORM_DATA) | - (mime::TEXT, mime::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 { @@ -394,11 +367,18 @@ pub fn is_forbidden_header_name(name: &str) -> bool { // [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 +// +// As of December 2019 WHATWG, isn't even using grammar productions for value; +// https://fetch.spec.whatg.org/#concept-header-value just says not to have +// newlines, nulls, or leading/trailing whitespace. fn validate_name_and_value(name: ByteString, value: ByteString) -> Fallible<(String, Vec<u8>)> { let valid_name = validate_name(name)?; + + // this is probably out of date if !is_field_content(&value) { return Err(Error::Type("Value is not valid".to_string())); } + Ok((valid_name, value.into())) } diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 913f5f8530a..959bca74b54 100755..100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -64,6 +64,13 @@ use std::cell::Cell; use style::attr::AttrValue; use style::str::split_html_space_chars; +use crate::dom::bindings::codegen::UnionTypes::RadioNodeListOrElement; +use crate::dom::radionodelist::RadioNodeList; +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); @@ -76,6 +83,7 @@ pub struct HTMLFormElement { elements: DomOnceCell<HTMLFormControlsCollection>, generation_id: Cell<GenerationId>, controls: DomRefCell<Vec<Dom<Element>>>, + past_names_map: DomRefCell<HashMap<DOMString, (Dom<Element>, Tm)>>, } impl HTMLFormElement { @@ -91,6 +99,7 @@ impl HTMLFormElement { elements: Default::default(), generation_id: Cell::new(GenerationId(0)), controls: DomRefCell::new(Vec::new()), + past_names_map: DomRefCell::new(HashMap::new()), } } @@ -253,6 +262,219 @@ impl HTMLFormElementMethods for HTMLFormElement { 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 mut candidates: Vec<DomRoot<Node>> = Vec::new(); + + let controls = self.controls.borrow(); + // Step 1 + for child in controls.iter() { + if child + .downcast::<HTMLElement>() + .map_or(false, |c| c.is_listed_element()) + { + if (child.has_attribute(&local_name!("id")) && + child.get_string_attribute(&local_name!("id")) == name) || + (child.has_attribute(&local_name!("name")) && + child.get_string_attribute(&local_name!("name")) == name) + { + candidates.push(DomRoot::from_ref(&*child.upcast::<Node>())); + } + } + } + // Step 2 + if candidates.len() == 0 { + for child in controls.iter() { + if child.is::<HTMLImageElement>() { + if (child.has_attribute(&local_name!("id")) && + child.get_string_attribute(&local_name!("id")) == name) || + (child.has_attribute(&local_name!("name")) && + child.get_string_attribute(&local_name!("name")) == name) + { + candidates.push(DomRoot::from_ref(&*child.upcast::<Node>())); + } + } + } + } + + let mut past_names_map = self.past_names_map.borrow_mut(); + + // Step 3 + if candidates.len() == 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.len() > 1 { + let window = window_from_node(self); + + return Some(RadioNodeListOrElement::RadioNodeList( + RadioNodeList::new_simple_list(&window, candidates.into_iter()), + )); + } + + // Step 5 + let element_node = &candidates[0]; + 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/#the-form-element:supported-property-names + 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: DOMString, + element: DomRoot<Element>, + source: SourcedNameSource, + } + + let mut sourcedNamesVec: 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 child.has_attribute(&local_name!("id")) { + let entry = SourcedName { + name: child.get_string_attribute(&local_name!("id")), + element: DomRoot::from_ref(&*child), + source: SourcedNameSource::Id, + }; + sourcedNamesVec.push(entry); + } + if child.has_attribute(&local_name!("name")) { + let entry = SourcedName { + name: child.get_string_attribute(&local_name!("name")), + element: DomRoot::from_ref(&*child), + source: SourcedNameSource::Name, + }; + sourcedNamesVec.push(entry); + } + } + } + + // Step 3 + for child in controls.iter() { + if child.is::<HTMLImageElement>() { + if child.has_attribute(&local_name!("id")) { + let entry = SourcedName { + name: child.get_string_attribute(&local_name!("id")), + element: DomRoot::from_ref(&*child), + source: SourcedNameSource::Id, + }; + sourcedNamesVec.push(entry); + } + if child.has_attribute(&local_name!("name")) { + let entry = SourcedName { + name: child.get_string_attribute(&local_name!("name")), + element: DomRoot::from_ref(&*child), + source: SourcedNameSource::Name, + }; + sourcedNamesVec.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 + }; + sourcedNamesVec.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 + + sourcedNamesVec.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 + sourcedNamesVec.retain(|sn| !sn.name.to_string().is_empty()); + + // Step 7-8 + let mut namesVec: Vec<DOMString> = Vec::new(); + for elem in sourcedNamesVec.iter() { + if namesVec + .iter() + .find(|name| name.to_string() == elem.name.to_string()) + .is_none() + { + namesVec.push(elem.name.clone()); + } + } + + return namesVec; + } } #[derive(Clone, Copy, MallocSizeOf, PartialEq)] @@ -357,7 +579,7 @@ impl HTMLFormElement { let encoding = self.pick_encoding(); // Step 9 - let mut form_data = match self.get_form_dataset(Some(submitter)) { + let mut form_data = match self.get_form_dataset(Some(submitter), Some(encoding)) { Some(form_data) => form_data, None => return, }; @@ -645,8 +867,14 @@ impl HTMLFormElement { } /// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set> + /// terminology note: "form data set" = "entry list" /// Steps range from 3 to 5 - fn get_unclean_dataset(&self, submitter: Option<FormSubmitter>) -> Vec<FormDatum> { + /// 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() { @@ -668,7 +896,7 @@ impl HTMLFormElement { 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(); @@ -705,7 +933,11 @@ impl HTMLFormElement { } /// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set> - pub fn get_form_dataset(&self, submitter: Option<FormSubmitter>) -> Option<Vec<FormDatum>> { + 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(); @@ -746,7 +978,7 @@ impl HTMLFormElement { self.constructing_entry_list.set(true); // Step 3-6 - let mut ret = self.get_unclean_dataset(submitter); + let mut ret = self.get_unclean_dataset(submitter, encoding); for datum in &mut ret { match &*datum.ty { "file" | "textarea" => (), // TODO diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 5fdbad5c1bd..9018f05813a 100755 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -49,6 +49,7 @@ use crate::textinput::{Direction, SelectionDirection, TextInput, UTF16CodeUnits, use caseless::compatibility_caseless_match_str; use dom_struct::dom_struct; use embedder_traits::FilterPattern; +use encoding_rs::Encoding; use html5ever::{LocalName, Prefix}; use msg::constellation_msg::InputMethodType; use net_traits::blob_url_store::get_blob_origin; @@ -929,14 +930,18 @@ 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> { + /// 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 + // Step 5.2 let ty = self.Type(); - // Step 3.4 + // Step 5.4 let name = self.Name(); let is_submitter = match submitter { Some(FormSubmitter::InputElement(s)) => self == s, @@ -944,12 +949,12 @@ impl HTMLInputElement { }; match self.input_type() { - // Step 3.1: it's a button but it is not submitter. + // Step 5.1: it's a button but it is not submitter. InputType::Submit | InputType::Button | InputType::Reset if !is_submitter => { return vec![]; }, - // Step 3.1: it's the "Checkbox" or "Radio Button" and whose checkedness is false. + // 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![]; @@ -959,7 +964,7 @@ impl HTMLInputElement { InputType::File => { let mut datums = vec![]; - // Step 3.2-3.7 + // Step 5.2-5.7 let name = self.Name(); match self.GetFiles() { @@ -988,7 +993,21 @@ impl HTMLInputElement { InputType::Image => return vec![], // Unimplemented - // Step 3.1: it's not the "Image Button" and doesn't have a name attribute. + // Step 5.10: it's a hidden field named _charset_ + InputType::Hidden => { + if name == "_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![]; @@ -996,7 +1015,7 @@ impl HTMLInputElement { }, } - // Step 3.9 + // Step 5.12 vec![FormDatum { ty: ty.clone(), name: name, @@ -1213,7 +1232,16 @@ impl HTMLInputElement { 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 => (), } } diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index 651084cfb84..91acd4b97e7 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -67,7 +67,7 @@ use crate::script_thread::ScriptThread; use crate::task_source::TaskSource; use dom_struct::dom_struct; use embedder_traits::resources::{self, Resource as EmbedderResource}; -use embedder_traits::{MediaSessionEvent, MediaSessionPlaybackState}; +use embedder_traits::{MediaPositionState, MediaSessionEvent, MediaSessionPlaybackState}; use euclid::default::Size2D; use headers::{ContentLength, ContentRange, HeaderMapExt}; use html5ever::{LocalName, Prefix}; @@ -1780,6 +1780,15 @@ impl HTMLMediaElement { .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())); @@ -1925,6 +1934,18 @@ impl HTMLMediaElement { 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] diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index dfac55a6f66..fdeca285e30 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -559,6 +559,10 @@ impl HTMLScriptElement { "{} is a module script. It should be fixed after #23545 landed.", url.clone() ); + self.global().issue_page_warning(&format!( + "Module scripts are not supported; {} will not be executed.", + url.clone() + )); }, } } else { @@ -578,6 +582,9 @@ impl HTMLScriptElement { "{} is a module script. It should be fixed after #23545 landed.", base_url.clone() ); + self.global().issue_page_warning( + "Module scripts are not supported; ignoring inline module script.", + ); return; } diff --git a/components/script/dom/identityhub.rs b/components/script/dom/identityhub.rs index 489eaacc78d..64e4cad3867 100644 --- a/components/script/dom/identityhub.rs +++ b/components/script/dom/identityhub.rs @@ -2,46 +2,121 @@ * License, v. 2.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 webgpu::wgpu::{AdapterId, Backend, DeviceId, IdentityManager, SurfaceId}; +use smallvec::SmallVec; +use webgpu::wgpu::{ + hub::IdentityManager, + id::{AdapterId, BufferId, DeviceId}, + Backend, +}; #[derive(Debug)] pub struct IdentityHub { - adapters: IdentityManager<AdapterId>, - devices: IdentityManager<DeviceId>, + adapters: IdentityManager, + devices: IdentityManager, + buffers: IdentityManager, + backend: Backend, } impl IdentityHub { fn new(backend: Backend) -> Self { IdentityHub { - adapters: IdentityManager::new(backend), - devices: IdentityManager::new(backend), + adapters: IdentityManager::default(), + devices: IdentityManager::default(), + buffers: IdentityManager::default(), + backend, } } + + fn create_adapter_id(&mut self) -> AdapterId { + self.adapters.alloc(self.backend) + } + + fn create_device_id(&mut self) -> DeviceId { + self.devices.alloc(self.backend) + } + + pub fn create_buffer_id(&mut self) -> BufferId { + self.buffers.alloc(self.backend) + } } #[derive(Debug)] pub struct Identities { - surface: IdentityManager<SurfaceId>, - hub: IdentityHub, + 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 { - let hub = if cfg!(any(target_os = "linux", target_os = "windows")) { - IdentityHub::new(Backend::Vulkan) - } else if cfg!(any(target_os = "ios", target_os = "macos")) { - IdentityHub::new(Backend::Metal) - } else { - IdentityHub::new(Backend::Empty) - }; - Identities { - surface: IdentityManager::new(Backend::Empty), - hub, + surface: IdentityManager::default(), + #[cfg(any(target_os = "linux", target_os = "windows"))] + vk_hub: IdentityHub::new(Backend::Vulkan), + #[cfg(target_os = "windows")] + dx12_hub: IdentityHub::new(Backend::Dx12), + #[cfg(target_os = "windows")] + dx11_hub: IdentityHub::new(Backend::Dx11), + #[cfg(any(target_os = "ios", target_os = "macos"))] + metal_hub: IdentityHub::new(Backend::Metal), + dummy_hub: IdentityHub::new(Backend::Empty), + } + } + + fn hubs(&mut self) -> Vec<&mut IdentityHub> { + vec![ + #[cfg(any(target_os = "linux", target_os = "windows"))] + &mut self.vk_hub, + #[cfg(target_os = "windows")] + &mut self.dx12_hub, + #[cfg(target_os = "windows")] + &mut self.dx11_hub, + #[cfg(any(target_os = "ios", target_os = "macos"))] + &mut self.metal_hub, + &mut self.dummy_hub, + ] + } + + pub fn create_device_id(&mut self, backend: Backend) -> DeviceId { + match backend { + #[cfg(any(target_os = "linux", target_os = "windows"))] + Backend::Vulkan => self.vk_hub.create_device_id(), + #[cfg(target_os = "windows")] + Backend::Dx12 => self.dx12_hub.create_device_id(), + #[cfg(target_os = "windows")] + Backend::Dx11 => self.dx11_hub.create_device_id(), + #[cfg(any(target_os = "ios", target_os = "macos"))] + Backend::Metal => self.metal_hub.create_device_id(), + _ => self.dummy_hub.create_device_id(), } } - pub fn create_adapter_id(&mut self) -> AdapterId { - self.hub.adapters.alloc() + pub fn create_adapter_ids(&mut self) -> SmallVec<[AdapterId; 4]> { + let mut ids = SmallVec::new(); + for hub in self.hubs() { + ids.push(hub.create_adapter_id()) + } + ids + } + + pub fn create_buffer_id(&mut self, backend: Backend) -> BufferId { + match backend { + #[cfg(any(target_os = "linux", target_os = "windows"))] + Backend::Vulkan => self.vk_hub.create_buffer_id(), + #[cfg(target_os = "windows")] + Backend::Dx12 => self.dx12_hub.create_buffer_id(), + #[cfg(target_os = "windows")] + Backend::Dx11 => self.dx11_hub.create_buffer_id(), + #[cfg(any(target_os = "ios", target_os = "macos"))] + Backend::Metal => self.metal_hub.create_buffer_id(), + _ => self.dummy_hub.create_buffer_id(), + } } } diff --git a/components/script/dom/macros.rs b/components/script/dom/macros.rs index 755624a8855..a1941662963 100644 --- a/components/script/dom/macros.rs +++ b/components/script/dom/macros.rs @@ -446,6 +446,7 @@ macro_rules! global_event_handlers( 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); diff --git a/components/script/dom/mediasession.rs b/components/script/dom/mediasession.rs index 1523e9a0ae6..8dfc88a1c04 100644 --- a/components/script/dom/mediasession.rs +++ b/components/script/dom/mediasession.rs @@ -9,10 +9,13 @@ use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaE use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataInit; use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataMethods; use crate::dom::bindings::codegen::Bindings::MediaSessionBinding; +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; @@ -194,6 +197,62 @@ impl MediaSessionMethods for MediaSession { 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 { diff --git a/components/script/dom/messageevent.rs b/components/script/dom/messageevent.rs index 9d9e5f88e35..cc5725ffb6c 100644 --- a/components/script/dom/messageevent.rs +++ b/components/script/dom/messageevent.rs @@ -2,6 +2,7 @@ * License, v. 2.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::EventBinding::EventMethods; use crate::dom::bindings::codegen::Bindings::MessageEventBinding; use crate::dom::bindings::codegen::Bindings::MessageEventBinding::MessageEventMethods; @@ -56,10 +57,10 @@ pub struct MessageEvent { event: Event, #[ignore_malloc_size_of = "mozjs"] data: Heap<JSVal>, - origin: DOMString, - source: Option<SrcObject>, - lastEventId: DOMString, - ports: Vec<DomRoot<MessagePort>>, + origin: DomRefCell<DOMString>, + source: DomRefCell<Option<SrcObject>>, + lastEventId: DomRefCell<DOMString>, + ports: DomRefCell<Vec<DomRoot<MessagePort>>>, } impl MessageEvent { @@ -85,10 +86,10 @@ impl MessageEvent { let ev = Box::new(MessageEvent { event: Event::new_inherited(), data: Heap::default(), - source: source.map(|source| source.into()), - origin, - lastEventId, - ports, + source: DomRefCell::new(source.map(|source| source.into())), + origin: DomRefCell::new(origin), + lastEventId: DomRefCell::new(lastEventId), + ports: DomRefCell::new(ports), }); let ev = reflect_dom_object(ev, global, MessageEventBinding::Wrap); ev.data.set(data.get()); @@ -187,12 +188,12 @@ impl MessageEventMethods for MessageEvent { /// <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 { + match &*self.source.borrow() { Some(SrcObject::WindowProxy(i)) => Some( WindowProxyOrMessagePortOrServiceWorker::WindowProxy(DomRoot::from_ref(&*i)), ), @@ -208,7 +209,7 @@ impl MessageEventMethods for MessageEvent { /// <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> @@ -218,6 +219,28 @@ impl MessageEventMethods for MessageEvent { /// <https://html.spec.whatwg.org/multipage/#dom-messageevent-ports> fn Ports(&self, cx: JSContext) -> JSVal { - message_ports_to_frozen_array(self.ports.as_slice(), cx) + message_ports_to_frozen_array(self.ports.borrow().as_slice(), cx) + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageevent-initmessageevent> + 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; + self.event + .init_event(Atom::from(type_), bubbles, cancelable); } } diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index d348633df88..b8bdc638c17 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -317,6 +317,9 @@ pub mod gamepadlist; pub mod globalscope; pub mod gpu; pub mod gpuadapter; +pub mod gpubuffer; +pub mod gpubufferusage; +pub mod gpudevice; pub mod hashchangeevent; pub mod headers; pub mod history; diff --git a/components/script/dom/navigator.rs b/components/script/dom/navigator.rs index 9121f7ef54d..03c6b27d156 100644 --- a/components/script/dom/navigator.rs +++ b/components/script/dom/navigator.rs @@ -24,9 +24,13 @@ use crate::dom::serviceworkercontainer::ServiceWorkerContainer; use crate::dom::window::Window; use crate::dom::xr::XR; use dom_struct::dom_struct; +use smallvec::SmallVec; use std::cell::RefCell; use std::rc::Rc; -use webgpu::wgpu::AdapterId; +use webgpu::wgpu::{ + id::{AdapterId, BufferId, DeviceId}, + Backend, +}; #[dom_struct] pub struct Navigator { @@ -73,8 +77,16 @@ impl Navigator { } impl Navigator { - pub fn create_adapter_id(&self) -> AdapterId { - self.gpu_id_hub.borrow_mut().create_adapter_id() + pub fn create_adapter_ids(&self) -> SmallVec<[AdapterId; 4]> { + self.gpu_id_hub.borrow_mut().create_adapter_ids() + } + + pub fn create_device_id(&self, backend: Backend) -> DeviceId { + self.gpu_id_hub.borrow_mut().create_device_id(backend) + } + + pub fn create_buffer_id(&self, backend: Backend) -> BufferId { + self.gpu_id_hub.borrow_mut().create_buffer_id(backend) } } diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 4885d87c64b..8ee51283146 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -2296,6 +2296,11 @@ 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<DomRoot<Document>> { match self.type_id() { diff --git a/components/script/dom/offscreencanvas.rs b/components/script/dom/offscreencanvas.rs index 2186ba54bc8..6d7cafed6f6 100644 --- a/components/script/dom/offscreencanvas.rs +++ b/components/script/dom/offscreencanvas.rs @@ -16,9 +16,12 @@ 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 ref_filter_map; use std::cell::Cell; use std::cell::Ref; @@ -34,22 +37,22 @@ pub enum OffscreenCanvasContext { #[dom_struct] pub struct OffscreenCanvas { eventtarget: EventTarget, - height: Cell<u64>, width: Cell<u64>, + height: Cell<u64>, context: DomRefCell<Option<OffscreenCanvasContext>>, placeholder: Option<Dom<HTMLCanvasElement>>, } impl OffscreenCanvas { pub fn new_inherited( - height: u64, width: u64, + height: u64, placeholder: Option<&HTMLCanvasElement>, ) -> OffscreenCanvas { OffscreenCanvas { eventtarget: EventTarget::new_inherited(), - height: Cell::new(height), width: Cell::new(width), + height: Cell::new(height), context: DomRefCell::new(None), placeholder: placeholder.map(Dom::from_ref), } @@ -57,12 +60,12 @@ impl OffscreenCanvas { pub fn new( global: &GlobalScope, - height: u64, width: u64, + height: u64, placeholder: Option<&HTMLCanvasElement>, ) -> DomRoot<OffscreenCanvas> { reflect_dom_object( - Box::new(OffscreenCanvas::new_inherited(height, width, placeholder)), + Box::new(OffscreenCanvas::new_inherited(width, height, placeholder)), global, OffscreenCanvasWrap, ) @@ -70,10 +73,10 @@ impl OffscreenCanvas { pub fn Constructor( global: &GlobalScope, - height: u64, width: u64, + height: u64, ) -> Fallible<DomRoot<OffscreenCanvas>> { - let offscreencanvas = OffscreenCanvas::new(global, height, width, None); + let offscreencanvas = OffscreenCanvas::new(global, width, height, None); Ok(offscreencanvas) } @@ -81,10 +84,44 @@ impl OffscreenCanvas { 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::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() { diff --git a/components/script/dom/offscreencanvasrenderingcontext2d.rs b/components/script/dom/offscreencanvasrenderingcontext2d.rs index 077752d9d6e..277b5925f27 100644 --- a/components/script/dom/offscreencanvasrenderingcontext2d.rs +++ b/components/script/dom/offscreencanvasrenderingcontext2d.rs @@ -24,8 +24,10 @@ 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 { @@ -72,6 +74,22 @@ impl OffscreenCanvasRenderingContext2D { pub fn set_canvas_bitmap_dimensions(&self, size: Size2D<u64>) { self.canvas_state.borrow().set_bitmap_dimensions(size); } + + pub fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) { + self.canvas_state.borrow().send_canvas_2d_msg(msg) + } + + pub fn origin_is_clean(&self) -> bool { + self.canvas_state.borrow().origin_is_clean() + } + + pub fn get_canvas_id(&self) -> CanvasId { + self.canvas_state.borrow().get_canvas_id() + } + + pub fn get_ipc_renderer(&self) -> IpcSender<CanvasMsg> { + self.canvas_state.borrow().get_ipc_renderer().clone() + } } impl OffscreenCanvasRenderingContext2DMethods for OffscreenCanvasRenderingContext2D { diff --git a/components/script/dom/performance.rs b/components/script/dom/performance.rs index 6c418b44316..b1f72f405f2 100644 --- a/components/script/dom/performance.rs +++ b/components/script/dom/performance.rs @@ -96,10 +96,10 @@ impl PerformanceEntryList { entry_type: Option<DOMString>, ) { self.entries.retain(|e| { - name.as_ref().map_or(true, |name_| *e.name() == *name_) && + name.as_ref().map_or(true, |name_| *e.name() != *name_) && entry_type .as_ref() - .map_or(true, |type_| *e.entry_type() == *type_) + .map_or(true, |type_| *e.entry_type() != *type_) }); } @@ -233,10 +233,10 @@ impl Performance { /// <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, add_to_performance_entries_buffer: bool) { + 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; + return None; } // Steps 1-3. @@ -253,19 +253,18 @@ impl Performance { } // Step 4. - // If the "add to performance entry buffer flag" is set, add the - // new entry to the buffer. - if add_to_performance_entries_buffer { - self.buffer - .borrow_mut() - .entries - .push(DomRoot::from_ref(entry)); - } + //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; + return None; } // Step 6. @@ -273,6 +272,8 @@ impl Performance { 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. @@ -321,7 +322,7 @@ impl Performance { .borrow_mut() .pop_front(); if let Some(ref entry) = entry { - self.queue_entry(entry, true); + self.queue_entry(entry); } else { break; } @@ -370,6 +371,12 @@ impl Performance { .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 { @@ -438,10 +445,7 @@ impl PerformanceMethods for Performance { // Steps 2 to 6. let entry = PerformanceMark::new(&global, mark_name, self.now(), 0.); // Steps 7 and 8. - self.queue_entry( - &entry.upcast::<PerformanceEntry>(), - true, /* buffer performance entry */ - ); + self.queue_entry(&entry.upcast::<PerformanceEntry>()); // Step 9. Ok(()) @@ -488,10 +492,7 @@ impl PerformanceMethods for Performance { ); // Step 9 and 10. - self.queue_entry( - &entry.upcast::<PerformanceEntry>(), - true, /* buffer performance entry */ - ); + self.queue_entry(&entry.upcast::<PerformanceEntry>()); // Step 11. Ok(()) diff --git a/components/script/dom/progressevent.rs b/components/script/dom/progressevent.rs index a1272baf8e3..c5f33dfe305 100644 --- a/components/script/dom/progressevent.rs +++ b/components/script/dom/progressevent.rs @@ -32,13 +32,6 @@ impl ProgressEvent { total: total, } } - pub fn new_uninitialized(global: &GlobalScope) -> DomRoot<ProgressEvent> { - reflect_dom_object( - Box::new(ProgressEvent::new_inherited(false, 0, 0)), - global, - ProgressEventBinding::Wrap, - ) - } pub fn new( global: &GlobalScope, type_: Atom, diff --git a/components/script/dom/request.rs b/components/script/dom/request.rs index d22d4680355..03e5dee683d 100644 --- a/components/script/dom/request.rs +++ b/components/script/dom/request.rs @@ -90,59 +90,63 @@ 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())); } - // Step 5.3 + // Step 6.3 let url = parsed_url.unwrap(); if includes_credentials(&url) { return Err(Error::Type("Url includes credentials".to_string())); } - // Step 5.4 + // Step 6.4 temporary_request = net_request_from_global(global, url); - // Step 5.5 + // Step 6.5 fallback_mode = Some(NetTraitsRequestMode::CorsMode); - // Step 5.6 + // Step 6.6 fallback_credentials = Some(NetTraitsRequestCredentials::CredentialsSameOrigin); }, - // Step 6 + // 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())); } - // 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())); } - // 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()); request.method = temporary_request.method; @@ -159,7 +163,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() || @@ -172,31 +176,33 @@ impl Request { init.referrerPolicy.is_some() || !init.window.handle().is_undefined() { - // Step 13.1 + // Step 14.1 if request.mode == NetTraitsRequestMode::Navigate { request.mode = NetTraitsRequestMode::SameOrigin; } - // Step 13.2 + // 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 = NetTraitsRequestReferrer::Client; - // Step 13.3 + // 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())); } - // 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" && @@ -205,55 +211,55 @@ impl Request { { request.referrer = NetTraitsRequestReferrer::Client; } else { - // Step 14.6 + // 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 + // Step 17 let mode = init .mode .as_ref() .map(|m| m.clone().into()) .or(fallback_mode); - // Step 17 + // 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 + // Step 20 let credentials = init .credentials .as_ref() .map(|m| m.clone().into()) .or(fallback_credentials); - // Step 20 + // 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( @@ -262,45 +268,55 @@ impl Request { } } - // 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) .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 + // Step 28 TODO: "If init["signal"] exists..." + + // Step 29 let r = Request::from_net_request(global, request); + + // Step 30 TODO: "If signal is not null..." + + // Step 31 + // "or_init" looks unclear here r.headers.or_init(|| Headers::for_request(&r.global())); - // Step 27 + // Step 32 - but spec says this should only be when non-empty init? + // Step 32.1 let mut headers_copy = r.Headers(); - // Step 28 + // Step 32.2 if let Some(possible_header) = init.headers.as_ref() { match possible_header { &HeadersInit::Headers(ref init_headers) => { @@ -319,7 +335,7 @@ impl Request { } } - // 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, @@ -328,21 +344,21 @@ impl Request { // 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 + // Step 32.4.2 r.Headers().set_guard(Guard::RequestNoCors); } - // Step 31 + // Step 32.5 match init.headers { None => { // This is equivalent to the specification's concept of @@ -360,10 +376,11 @@ impl Request { _ => {}, } + // 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 32 + // Step 33 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() @@ -371,7 +388,7 @@ impl Request { 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(); @@ -392,14 +409,16 @@ impl Request { } } - // Step 34 + // Step 35-36 if let Some(Some(ref init_body)) = init.body { - // Step 34.2 + // Step 36.2 TODO "If init["keepalive"] exists and is true..." + + // Step 36.3 let extracted_body_tmp = init_body.extract(); input_body = Some(extracted_body_tmp.0); let content_type = extracted_body_tmp.1; - // Step 34.3 + // Step 36.4 if let Some(contents) = content_type { if !r .Headers() @@ -414,17 +433,23 @@ impl Request { } } - // 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) } diff --git a/components/script/dom/response.rs b/components/script/dom/response.rs index 184e5ff8888..0e756513b9a 100644 --- a/components/script/dom/response.rs +++ b/components/script/dom/response.rs @@ -19,6 +19,7 @@ use crate::dom::headers::{is_obs_text, is_vchar}; use crate::dom::headers::{Guard, Headers}; use crate::dom::promise::Promise; use crate::dom::xmlhttprequest::Extractable; +use crate::script_runtime::StreamConsumer; use dom_struct::dom_struct; use http::header::HeaderMap as HyperHeaders; use hyper::StatusCode; @@ -48,6 +49,8 @@ pub struct Response { body: DomRefCell<NetTraitsResponseBody>, #[ignore_malloc_size_of = "Rc"] body_promise: DomRefCell<Option<(Rc<Promise>, BodyType)>>, + #[ignore_malloc_size_of = "StreamConsumer"] + stream_consumer: DomRefCell<Option<StreamConsumer>>, } impl Response { @@ -64,6 +67,7 @@ impl Response { url_list: DomRefCell::new(vec![]), body: DomRefCell::new(NetTraitsResponseBody::Empty), body_promise: DomRefCell::new(None), + stream_consumer: DomRefCell::new(None), } } @@ -441,11 +445,24 @@ impl Response { } } + pub fn set_stream_consumer(&self, sc: Option<StreamConsumer>) { + *self.stream_consumer.borrow_mut() = sc; + } + + pub fn stream_chunk(&self, stream: &[u8]) { + if let Some(stream_consumer) = self.stream_consumer.borrow_mut().as_ref() { + stream_consumer.consume_chunk(stream); + } + } + #[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); } + if let Some(stream_consumer) = self.stream_consumer.borrow_mut().take() { + stream_consumer.stream_end(); + } } } diff --git a/components/script/dom/serviceworkerglobalscope.rs b/components/script/dom/serviceworkerglobalscope.rs index 2a3f4d7041a..ada042c17ac 100644 --- a/components/script/dom/serviceworkerglobalscope.rs +++ b/components/script/dom/serviceworkerglobalscope.rs @@ -289,7 +289,7 @@ impl ServiceWorkerGlobalScope { .credentials_mode(CredentialsMode::Include) .parser_metadata(ParserMetadata::NotParserInserted) .use_url_credentials(true) - .pipeline_id(pipeline_id) + .pipeline_id(Some(pipeline_id)) .referrer(referrer) .referrer_policy(referrer_policy) .origin(origin); @@ -371,9 +371,6 @@ impl ServiceWorkerGlobalScope { 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) }, diff --git a/components/script/dom/serviceworkerregistration.rs b/components/script/dom/serviceworkerregistration.rs index a2165a82115..647fb9518c5 100644 --- a/components/script/dom/serviceworkerregistration.rs +++ b/components/script/dom/serviceworkerregistration.rs @@ -16,10 +16,12 @@ 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::{ScopeThings, WorkerScriptLoadOrigin}; use servo_url::ServoUrl; use std::cell::Cell; +use uuid::Uuid; #[dom_struct] pub struct ServiceWorkerRegistration { @@ -108,10 +110,10 @@ 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); ScopeThings { diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index 004daeed9be..b968aa47274 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -689,6 +689,8 @@ pub struct ParserContext { url: ServoUrl, /// timing data for this resource resource_timing: ResourceFetchTiming, + /// pushed entry index + pushed_entry_index: Option<usize>, } impl ParserContext { @@ -699,6 +701,7 @@ impl ParserContext { id: id, url: url, resource_timing: ResourceFetchTiming::new(ResourceTimingType::Navigation), + pushed_entry_index: None, } } } @@ -774,6 +777,8 @@ impl FetchResponseListener for ParserContext { self.parser = Some(Trusted::new(&*parser)); + self.submit_resource_timing(); + match content_type { Some(ref mime) if mime.type_() == mime::IMAGE => { self.is_synthesized_document = true; @@ -881,8 +886,16 @@ impl FetchResponseListener for ParserContext { parser.parse_sync(); } - //TODO only submit if this is the current document resource - self.submit_resource_timing(); + //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 { @@ -908,10 +921,10 @@ impl FetchResponseListener for ParserContext { //TODO nav_start and nav_start_precise let performance_entry = PerformanceNavigationTiming::new(&document.global(), 0, 0, &document); - document + self.pushed_entry_index = document .global() .performance() - .queue_entry(performance_entry.upcast::<PerformanceEntry>(), true); + .queue_entry(performance_entry.upcast::<PerformanceEntry>()); } } diff --git a/components/script/dom/webglcontextevent.rs b/components/script/dom/webglcontextevent.rs index 5bc153ddccd..e4b0efe0360 100644 --- a/components/script/dom/webglcontextevent.rs +++ b/components/script/dom/webglcontextevent.rs @@ -42,18 +42,6 @@ impl WebGLContextEvent { } } - pub fn new_uninitialized(window: &Window) -> DomRoot<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::new(WebGLContextEvent::new_inherited(status_message)), - window, - WebGLContextEventBinding::Wrap, - ) - } - pub fn new( window: &Window, type_: Atom, diff --git a/components/script/dom/webidls/CanvasRenderingContext2D.webidl b/components/script/dom/webidls/CanvasRenderingContext2D.webidl index 743661cdf53..52e532e3a8b 100644 --- a/components/script/dom/webidls/CanvasRenderingContext2D.webidl +++ b/components/script/dom/webidls/CanvasRenderingContext2D.webidl @@ -12,7 +12,7 @@ typedef (HTMLOrSVGImageElement or /*HTMLVideoElement or*/ HTMLCanvasElement or /*ImageBitmap or*/ - /*OffscreenCanvas or*/ + OffscreenCanvas or /*CSSImageValue*/ CSSStyleValue) CanvasImageSource; enum CanvasFillRule { "nonzero", "evenodd" }; diff --git a/components/script/dom/webidls/DOMException.webidl b/components/script/dom/webidls/DOMException.webidl index 8475238cb9b..d61d30151fa 100644 --- a/components/script/dom/webidls/DOMException.webidl +++ b/components/script/dom/webidls/DOMException.webidl @@ -47,6 +47,4 @@ interface DOMException { // A custom message set by the thrower. readonly attribute DOMString message; - - stringifier; }; diff --git a/components/script/dom/webidls/DOMTokenList.webidl b/components/script/dom/webidls/DOMTokenList.webidl index ed4a541f730..b2d604ee2a9 100644 --- a/components/script/dom/webidls/DOMTokenList.webidl +++ b/components/script/dom/webidls/DOMTokenList.webidl @@ -19,7 +19,7 @@ interface DOMTokenList { [CEReactions, Throws] boolean toggle(DOMString token, optional boolean force); [CEReactions, Throws] - void replace(DOMString token, DOMString newToken); + boolean replace(DOMString token, DOMString newToken); [CEReactions, Pure] stringifier attribute DOMString value; diff --git a/components/script/dom/webidls/EventHandler.webidl b/components/script/dom/webidls/EventHandler.webidl index b5566db7153..53df17fe7e0 100644 --- a/components/script/dom/webidls/EventHandler.webidl +++ b/components/script/dom/webidls/EventHandler.webidl @@ -51,6 +51,7 @@ interface mixin GlobalEventHandlers { attribute EventHandler onended; attribute OnErrorEventHandler onerror; attribute EventHandler onfocus; + attribute EventHandler onformdata; attribute EventHandler oninput; attribute EventHandler oninvalid; attribute EventHandler onkeydown; diff --git a/components/script/dom/webidls/FakeXRDevice.webidl b/components/script/dom/webidls/FakeXRDevice.webidl index bb008bdc73f..4dde2678435 100644 --- a/components/script/dom/webidls/FakeXRDevice.webidl +++ b/components/script/dom/webidls/FakeXRDevice.webidl @@ -13,8 +13,11 @@ interface FakeXRDevice { // // behaves as if device was disconnected // Promise<void> disconnect(); - // Sets the origin of the viewer [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); @@ -40,6 +43,8 @@ dictionary FakeXRViewInit { required FakeXRRigidTransformInit viewOffset; // https://immersive-web.github.io/webxr/#dom-xrwebgllayer-getviewport required FakeXRDeviceResolution resolution; + + FakeXRFieldOfViewInit fieldOfView; }; // https://immersive-web.github.io/webxr/#xrviewport @@ -56,3 +61,10 @@ dictionary FakeXRRigidTransformInit { required sequence<float> position; required sequence<float> orientation; }; + +dictionary FakeXRFieldOfViewInit { + required float upDegrees; + required float downDegrees; + required float leftDegrees; + required float rightDegrees; +}; diff --git a/components/script/dom/webidls/GPUAdapter.webidl b/components/script/dom/webidls/GPUAdapter.webidl index 0c2118a5f56..cc664fabffa 100644 --- a/components/script/dom/webidls/GPUAdapter.webidl +++ b/components/script/dom/webidls/GPUAdapter.webidl @@ -10,5 +10,25 @@ interface GPUAdapter { //readonly attribute GPULimits limits; Don’t expose higher limits for now. // May reject with DOMException // TODO: DOMException("OperationError")? - // Promise<GPUDevice> requestDevice(optional GPUDeviceDescriptor descriptor = {}); + Promise<GPUDevice> requestDevice(optional GPUDeviceDescriptor descriptor = {}); +}; + +dictionary GPUDeviceDescriptor : GPUObjectDescriptorBase { + GPUExtensions extensions = {}; + GPULimits limits = {}; +}; + +dictionary GPUExtensions { + boolean anisotropicFiltering = false; +}; + +dictionary GPULimits { + unsigned long maxBindGroups = 4; + unsigned long maxDynamicUniformBuffersPerPipelineLayout = 8; + unsigned long maxDynamicStorageBuffersPerPipelineLayout = 4; + unsigned long maxSampledTexturesPerShaderStage = 16; + unsigned long maxSamplersPerShaderStage = 16; + unsigned long maxStorageBuffersPerShaderStage = 4; + unsigned long maxStorageTexturesPerShaderStage = 4; + unsigned long maxUniformBuffersPerShaderStage = 12; }; diff --git a/components/script/dom/webidls/GPUBuffer.webidl b/components/script/dom/webidls/GPUBuffer.webidl new file mode 100644 index 00000000000..0b327bdcbd2 --- /dev/null +++ b/components/script/dom/webidls/GPUBuffer.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://gpuweb.github.io/gpuweb/#gpubuffer +[Exposed=(Window, DedicatedWorker), Serializable, Pref="dom.webgpu.enabled"] +interface GPUBuffer { + // Promise<ArrayBuffer> mapReadAsync(); + // Promise<ArrayBuffer> mapWriteAsync(); + void unmap(); + + void destroy(); +}; +GPUBuffer includes GPUObjectBase; + +dictionary GPUBufferDescriptor : GPUObjectDescriptorBase { + required GPUBufferSize size; + required GPUBufferUsageFlags usage; +}; + +typedef unsigned long long GPUBufferSize; + +typedef unsigned long GPUBufferUsageFlags; + +typedef sequence<any> GPUMappedBuffer; diff --git a/components/script/dom/webidls/GPUBufferUsage.webidl b/components/script/dom/webidls/GPUBufferUsage.webidl new file mode 100644 index 00000000000..549f6652d1f --- /dev/null +++ b/components/script/dom/webidls/GPUBufferUsage.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/#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; +}; diff --git a/components/script/dom/webidls/GPUDevice.webidl b/components/script/dom/webidls/GPUDevice.webidl new file mode 100644 index 00000000000..965f7e1b7d5 --- /dev/null +++ b/components/script/dom/webidls/GPUDevice.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/#gpudevice +[Exposed=(Window, DedicatedWorker)/*, Serializable */, Pref="dom.webgpu.enabled"] +interface GPUDevice : EventTarget { + readonly attribute GPUAdapter adapter; + readonly attribute object extensions; + readonly attribute object limits; + + GPUBuffer createBuffer(GPUBufferDescriptor descriptor); + GPUMappedBuffer createBufferMapped(GPUBufferDescriptor descriptor); + /*Promise<GPUMappedBuffer> createBufferMappedAsync(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); + + GPUCommandEncoder createCommandEncoder(optional GPUCommandEncoderDescriptor descriptor = {}); + GPURenderBundleEncoder createRenderBundleEncoder(GPURenderBundleEncoderDescriptor descriptor); + + GPUQueue getQueue();*/ +}; +GPUDevice includes GPUObjectBase; diff --git a/components/script/dom/webidls/GPUObjectBase.webidl b/components/script/dom/webidls/GPUObjectBase.webidl new file mode 100644 index 00000000000..c9a35992fb8 --- /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 DOMString? label; +}; + +dictionary GPUObjectDescriptorBase { + DOMString? label; +}; diff --git a/components/script/dom/webidls/HTMLFormElement.webidl b/components/script/dom/webidls/HTMLFormElement.webidl index f48726c343a..13ffbf4dfe8 100644 --- a/components/script/dom/webidls/HTMLFormElement.webidl +++ b/components/script/dom/webidls/HTMLFormElement.webidl @@ -29,7 +29,7 @@ interface HTMLFormElement : HTMLElement { [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(); [CEReactions] diff --git a/components/script/dom/webidls/MediaSession.webidl b/components/script/dom/webidls/MediaSession.webidl index 12b3fe062ba..2680ea0c40b 100644 --- a/components/script/dom/webidls/MediaSession.webidl +++ b/components/script/dom/webidls/MediaSession.webidl @@ -42,6 +42,12 @@ dictionary MediaSessionSeekToActionDetails : MediaSessionActionDetails { boolean? fastSeek; }; +dictionary MediaPositionState { + double duration; + double playbackRate; + double position; +}; + callback MediaSessionActionHandler = void(/*MediaSessionActionDetails details*/); [Exposed=Window] @@ -52,6 +58,5 @@ interface MediaSession { void setActionHandler(MediaSessionAction action, MediaSessionActionHandler? handler); - //void setPositionState(optional MediaPositionState? state); + [Throws] void setPositionState(optional MediaPositionState state = {}); }; - diff --git a/components/script/dom/webidls/MessageEvent.webidl b/components/script/dom/webidls/MessageEvent.webidl index 1d4699cbe3c..b37d725b793 100644 --- a/components/script/dom/webidls/MessageEvent.webidl +++ b/components/script/dom/webidls/MessageEvent.webidl @@ -11,6 +11,17 @@ interface MessageEvent : Event { readonly attribute DOMString lastEventId; 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 { diff --git a/components/script/dom/webidls/Node.webidl b/components/script/dom/webidls/Node.webidl index 6879518b017..c8a71dbebb7 100644 --- a/components/script/dom/webidls/Node.webidl +++ b/components/script/dom/webidls/Node.webidl @@ -29,6 +29,9 @@ interface Node : EventTarget { readonly attribute USVString baseURI; [Pure] + readonly attribute boolean isConnected; + + [Pure] readonly attribute Document? ownerDocument; [Pure] diff --git a/components/script/dom/webidls/Performance.webidl b/components/script/dom/webidls/Performance.webidl index 0e2f7a960d5..5db6551ff96 100644 --- a/components/script/dom/webidls/Performance.webidl +++ b/components/script/dom/webidls/Performance.webidl @@ -34,23 +34,20 @@ partial interface Performance { [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 { void clearResourceTimings (); void setResourceTimingBufferSize (unsigned long maxSize); attribute EventHandler onresourcetimingbufferfull; }; -// FIXME(avada): this should be deprecated, but is currently included for web compat -// https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html#performance-timing-attribute -[Exposed=(Window)] -partial interface Performance { - PerformanceNavigationTiming timing(); -}; + // https://w3c.github.io/navigation-timing/#extensions-to-the-performance-interface +[Exposed=Window] partial interface Performance { - [SameObject, Exposed=Window] + [SameObject] + readonly attribute PerformanceNavigationTiming timing; + [SameObject] readonly attribute PerformanceNavigation navigation; }; diff --git a/components/script/dom/webidls/XRRenderState.webidl b/components/script/dom/webidls/XRRenderState.webidl index 01d7fd39191..e361ced6597 100644 --- a/components/script/dom/webidls/XRRenderState.webidl +++ b/components/script/dom/webidls/XRRenderState.webidl @@ -7,11 +7,13 @@ dictionary XRRenderStateInit { double depthNear; double depthFar; + double inlineVerticalFieldOfView; XRWebGLLayer baseLayer; }; [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; }; diff --git a/components/script/dom/webidls/XRWebGLLayer.webidl b/components/script/dom/webidls/XRWebGLLayer.webidl index 40091ccc73e..dd2420dd13a 100644 --- a/components/script/dom/webidls/XRWebGLLayer.webidl +++ b/components/script/dom/webidls/XRWebGLLayer.webidl @@ -30,7 +30,7 @@ interface XRWebGLLayer { readonly attribute boolean stencil; readonly attribute boolean alpha; - readonly attribute WebGLFramebuffer framebuffer; + readonly attribute WebGLFramebuffer? framebuffer; readonly attribute unsigned long framebufferWidth; readonly attribute unsigned long framebufferHeight; diff --git a/components/script/dom/worker.rs b/components/script/dom/worker.rs index f08bdcab79e..2d126933849 100644 --- a/components/script/dom/worker.rs +++ b/components/script/dom/worker.rs @@ -26,7 +26,7 @@ use crate::dom::workerglobalscope::prepare_workerscope_init; use crate::script_runtime::JSContext; use crate::task::TaskOnce; use crossbeam_channel::{unbounded, Sender}; -use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg}; +use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg, WorkerId}; use dom_struct::dom_struct; use ipc_channel::ipc; use js::jsapi::{Heap, JSObject, JS_RequestInterruptCallback}; @@ -36,6 +36,7 @@ use script_traits::{StructuredSerializedData, WorkerScriptLoadOrigin}; use std::cell::Cell; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; +use uuid::Uuid; pub type TrustedWorkerAddress = Trusted<Worker>; @@ -96,11 +97,11 @@ 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 (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); diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index e943b6b0ef4..8624208423e 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -58,6 +58,7 @@ use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use time::precise_time_ns; +use uuid::Uuid; pub fn prepare_workerscope_init( global: &GlobalScope, @@ -71,7 +72,7 @@ pub fn prepare_workerscope_init( from_devtools_sender: devtools_sender, script_to_constellation_chan: global.script_to_constellation_chan().clone(), scheduler_chan: global.scheduler_chan().clone(), - worker_id: global.get_next_worker_id(), + worker_id: WorkerId(Uuid::new_v4()), pipeline_id: global.pipeline_id(), origin: global.origin().immutable().clone(), is_headless: global.is_headless(), diff --git a/components/script/dom/xr.rs b/components/script/dom/xr.rs index a916d7a8267..72cc573d645 100644 --- a/components/script/dom/xr.rs +++ b/components/script/dom/xr.rs @@ -42,6 +42,7 @@ pub struct XR { 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>, } @@ -53,6 +54,7 @@ impl XR { 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(), } } @@ -86,7 +88,9 @@ impl XR { self.active_immersive_session.set(None); } } - // XXXManishearth when we support inline sessions we should remove them too + self.active_inline_sessions + .borrow_mut() + .retain(|sess| Dom::from_ref(&**sess) != Dom::from_ref(session)); } } @@ -158,17 +162,19 @@ impl XRMethods for XR { ) -> Rc<Promise> { let promise = Promise::new_in_current_compartment(&self.global(), comp); - if !ScriptThread::is_user_interacting() { - promise.reject_error(Error::Security); - return promise; - } + if mode != XRSessionMode::Inline { + if !ScriptThread::is_user_interacting() { + promise.reject_error(Error::Security); + return promise; + } - if self.pending_or_active_session() { - promise.reject_error(Error::InvalidState); - return promise; - } + if self.pending_or_active_session() { + promise.reject_error(Error::InvalidState); + return promise; + } - self.set_pending(); + self.set_pending(); + } let promise = Promise::new_in_current_compartment(&self.global(), comp); let mut trusted = Some(TrustedPromise::new(promise.clone())); @@ -193,7 +199,7 @@ impl XRMethods for XR { }; let _ = task_source.queue_with_canceller( task!(request_session: move || { - this.root().session_obtained(message, trusted.root()); + this.root().session_obtained(message, trusted.root(), mode); }), &canceller, ); @@ -211,7 +217,12 @@ impl XRMethods for XR { } impl XR { - fn session_obtained(&self, response: Result<Session, XRError>, promise: Rc<Promise>) { + fn session_obtained( + &self, + response: Result<Session, XRError>, + promise: Rc<Promise>, + mode: XRSessionMode, + ) { let session = match response { Ok(session) => session, Err(_) => { @@ -220,8 +231,14 @@ impl XR { }, }; - let session = XRSession::new(&self.global(), session); - self.set_active_immersive_session(&session); + let session = XRSession::new(&self.global(), session, mode); + 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); } diff --git a/components/script/dom/xrframe.rs b/components/script/dom/xrframe.rs index 6c67d896f81..9a752abf759 100644 --- a/components/script/dom/xrframe.rs +++ b/components/script/dom/xrframe.rs @@ -77,7 +77,11 @@ impl XRFrameMethods for XRFrame { return Err(Error::InvalidState); } - let pose = reference.get_viewer_pose(&self.data); + let pose = if let Some(pose) = reference.get_viewer_pose(&self.data) { + pose + } else { + return Ok(None); + }; Ok(Some(XRViewerPose::new(&self.global(), &self.session, pose))) } diff --git a/components/script/dom/xrreferencespace.rs b/components/script/dom/xrreferencespace.rs index 35e106d1125..1116407cf46 100644 --- a/components/script/dom/xrreferencespace.rs +++ b/components/script/dom/xrreferencespace.rs @@ -10,10 +10,10 @@ 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, ApiRigidTransform, ApiViewerPose, XRSession}; +use crate::dom::xrsession::{cast_transform, ApiPose, ApiViewerPose, XRSession}; use crate::dom::xrspace::XRSpace; use dom_struct::dom_struct; -use euclid::{RigidTransform3D, Vector3D}; +use euclid::RigidTransform3D; use webxr_api::Frame; #[dom_struct] @@ -80,8 +80,8 @@ impl XRReferenceSpace { /// /// This is equivalent to `get_pose(self).inverse() * get_pose(viewerSpace)` (in column vector notation), /// however we specialize it to be efficient - pub fn get_viewer_pose(&self, base_pose: &Frame) -> ApiViewerPose { - let pose = self.get_unoffset_viewer_pose(base_pose); + pub fn get_viewer_pose(&self, base_pose: &Frame) -> Option<ApiViewerPose> { + let pose = self.get_unoffset_viewer_pose(base_pose)?; // in column-vector notation, // get_viewer_pose(space) = get_pose(space).inverse() * get_pose(viewer_space) // = (get_unoffset_pose(space) * offset).inverse() * get_pose(viewer_space) @@ -89,14 +89,13 @@ impl XRReferenceSpace { // = offset.inverse() * get_unoffset_viewer_pose(space) let offset = self.offset.transform(); let inverse = offset.inverse(); - inverse.pre_transform(&pose) + Some(inverse.pre_transform(&pose)) } /// Gets pose of the viewer with respect to this space /// /// Does not apply originOffset, use get_viewer_pose instead if you need it - pub fn get_unoffset_viewer_pose(&self, base_pose: &Frame) -> ApiViewerPose { - let viewer_pose: ApiViewerPose = cast_transform(base_pose.transform); + pub fn get_unoffset_viewer_pose(&self, base_pose: &Frame) -> Option<ApiViewerPose> { // all math is in column-vector notation // we use the following equation to verify correctness here: // get_viewer_pose(space) = get_pose(space).inverse() * get_pose(viewer_space) @@ -105,25 +104,27 @@ impl XRReferenceSpace { // get_viewer_pose(eye_level) = get_pose(eye_level).inverse() * get_pose(viewer_space) // = I * viewer_pose // = viewer_pose + let viewer_pose: ApiViewerPose = cast_transform(base_pose.transform?); // we get viewer poses in eye-level space by default - viewer_pose + Some(viewer_pose) }, XRReferenceSpaceType::Local_floor => { - // XXXManishearth support getting floor info from stage parameters - // get_viewer_pose(floor_level) = get_pose(floor_level).inverse() * get_pose(viewer_space) - // = Translate(-2).inverse() * viewer_pose - // = Translate(2) * viewer_pose + // = floor_to_native.inverse() * viewer_pose + // = native_to_floor * viewer_pose + let viewer_pose = base_pose.transform?; + let native_to_floor = self + .upcast::<XRSpace>() + .session() + .with_session(|s| s.floor_transform())?; - // assume approximate user height of 2 meters - let floor_to_eye: ApiRigidTransform = Vector3D::new(0., 2., 0.).into(); - floor_to_eye.pre_transform(&viewer_pose) + Some(cast_transform(native_to_floor.pre_transform(&viewer_pose))) }, XRReferenceSpaceType::Viewer => { // This reference space follows the viewer around, so the viewer is // always at an identity transform with respect to it - RigidTransform3D::identity() + Some(RigidTransform3D::identity()) }, _ => unimplemented!(), } @@ -134,34 +135,34 @@ impl XRReferenceSpace { /// 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) -> ApiPose { - let pose = self.get_unoffset_pose(base_pose); + 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 - pose.pre_transform(&offset) + 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) -> ApiPose { + 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) - RigidTransform3D::identity() + Some(RigidTransform3D::identity()) }, XRReferenceSpaceType::Local_floor => { - // XXXManishearth support getting floor info from stage parameters - - // Assume approximate height of 2m - // the floor-level space is 2m below the eye-level space, which is (0, 0, 0) - Vector3D::new(0., -2., 0.).into() + let native_to_floor = self + .upcast::<XRSpace>() + .session() + .with_session(|s| s.floor_transform())?; + Some(cast_transform(native_to_floor.inverse())) }, - XRReferenceSpaceType::Viewer => cast_transform(base_pose.transform), + XRReferenceSpaceType::Viewer => base_pose.transform.map(cast_transform), _ => unimplemented!(), } } diff --git a/components/script/dom/xrrenderstate.rs b/components/script/dom/xrrenderstate.rs index 2aba4e46493..bebcfd7de61 100644 --- a/components/script/dom/xrrenderstate.rs +++ b/components/script/dom/xrrenderstate.rs @@ -17,6 +17,7 @@ pub struct XRRenderState { reflector_: Reflector, depth_near: Cell<f64>, depth_far: Cell<f64>, + inline_vertical_fov: Cell<Option<f64>>, layer: MutNullableDom<XRWebGLLayer>, } @@ -24,12 +25,14 @@ impl XRRenderState { pub fn new_inherited( depth_near: f64, depth_far: f64, + inline_vertical_fov: Option<f64>, layer: Option<&XRWebGLLayer>, ) -> XRRenderState { XRRenderState { reflector_: Reflector::new(), depth_near: Cell::new(depth_near), depth_far: Cell::new(depth_far), + inline_vertical_fov: Cell::new(inline_vertical_fov), layer: MutNullableDom::new(layer), } } @@ -38,10 +41,16 @@ impl XRRenderState { global: &GlobalScope, depth_near: f64, depth_far: f64, + inline_vertical_fov: Option<f64>, layer: Option<&XRWebGLLayer>, ) -> DomRoot<XRRenderState> { reflect_dom_object( - Box::new(XRRenderState::new_inherited(depth_near, depth_far, layer)), + Box::new(XRRenderState::new_inherited( + depth_near, + depth_far, + inline_vertical_fov, + layer, + )), global, XRRenderStateBinding::Wrap, ) @@ -52,6 +61,7 @@ impl XRRenderState { &self.global(), self.depth_near.get(), self.depth_far.get(), + self.inline_vertical_fov.get(), self.layer.get().as_ref().map(|x| &**x), ) } @@ -62,6 +72,10 @@ impl XRRenderState { 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_layer(&self, layer: Option<&XRWebGLLayer>) { self.layer.set(layer) } @@ -78,6 +92,11 @@ impl XRRenderStateMethods for XRRenderState { 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.layer.get() diff --git a/components/script/dom/xrsession.rs b/components/script/dom/xrsession.rs index 438705853f6..6e076b6daba 100644 --- a/components/script/dom/xrsession.rs +++ b/components/script/dom/xrsession.rs @@ -8,6 +8,7 @@ use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorBinding::NavigatorMethods; use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::codegen::Bindings::XRBinding::XRSessionMode; use crate::dom::bindings::codegen::Bindings::XRReferenceSpaceBinding::XRReferenceSpaceType; use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::XRRenderStateInit; use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::XRRenderStateMethods; @@ -39,16 +40,18 @@ use crate::dom::xrspace::XRSpace; use crate::dom::xrwebgllayer::XRWebGLLayer; use crate::task_source::TaskSource; use dom_struct::dom_struct; -use euclid::RigidTransform3D; +use euclid::{Rect, RigidTransform3D, Transform3D}; use ipc_channel::ipc::IpcSender; use ipc_channel::router::ROUTER; +use metrics::ToMs; use profile_traits::ipc; use std::cell::Cell; +use std::f64::consts::{FRAC_PI_2, PI}; use std::mem; use std::rc::Rc; use webxr_api::{ - self, EnvironmentBlendMode, Event as XREvent, Frame, SelectEvent, SelectKind, Session, - Visibility, + self, util, Display, EnvironmentBlendMode, Event as XREvent, Frame, SelectEvent, SelectKind, + Session, View, Viewer, Visibility, }; #[dom_struct] @@ -56,6 +59,7 @@ pub struct XRSession { eventtarget: EventTarget, base_layer: MutNullableDom<XRWebGLLayer>, blend_mode: XREnvironmentBlendMode, + mode: XRSessionMode, visibility_state: Cell<XRVisibilityState>, viewer_space: MutNullableDom<XRSpace>, #[ignore_malloc_size_of = "defined in webxr"] @@ -63,12 +67,14 @@ pub struct XRSession { 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 = "defined in ipc-channel"] - raf_sender: DomRefCell<Option<IpcSender<(f64, Frame)>>>, + raf_sender: DomRefCell<Option<IpcSender<Frame>>>, input_sources: Dom<XRInputSourceArray>, // Any promises from calling end() #[ignore_malloc_size_of = "promises are hard"] @@ -85,17 +91,20 @@ impl XRSession { session: Session, render_state: &XRRenderState, input_sources: &XRInputSourceArray, + mode: XRSessionMode, ) -> XRSession { XRSession { eventtarget: EventTarget::new_inherited(), base_layer: Default::default(), 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![]), @@ -107,14 +116,20 @@ impl XRSession { } } - pub fn new(global: &GlobalScope, session: Session) -> DomRoot<XRSession> { - let render_state = XRRenderState::new(global, 0.1, 1000.0, None); + pub fn new(global: &GlobalScope, session: Session, mode: XRSessionMode) -> 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); let input_sources = XRInputSourceArray::new(global); let ret = reflect_dom_object( Box::new(XRSession::new_inherited( session, &render_state, &input_sources, + mode, )), global, XRSessionBinding::Wrap, @@ -134,6 +149,10 @@ impl XRSession { self.ended.get() } + pub fn is_immersive(&self) -> bool { + self.mode != XRSessionMode::Inline + } + fn setup_raf_loop(&self) { assert!( self.raf_sender.borrow().is_none(), @@ -287,7 +306,7 @@ impl XRSession { } /// https://immersive-web.github.io/webxr/#xr-animation-frame - fn raf_callback(&self, (time, mut frame): (f64, Frame)) { + fn raf_callback(&self, mut frame: Frame) { debug!("WebXR RAF callback"); // Step 1 @@ -298,9 +317,12 @@ impl XRSession { self.active_render_state.set(&pending); // Step 6-7: XXXManishearth handle inlineVerticalFieldOfView - // XXXManishearth handle inline sessions and composition disabled flag - let swap_chain_id = pending.GetBaseLayer().map(|layer| layer.swap_chain_id()); - self.session.borrow_mut().set_swap_chain(swap_chain_id); + if self.is_immersive() { + let swap_chain_id = pending.GetBaseLayer().map(|layer| layer.swap_chain_id()); + self.session.borrow_mut().set_swap_chain(swap_chain_id); + } else { + self.update_inline_projection_matrix() + } } for event in frame.events.drain(..) { @@ -317,6 +339,8 @@ impl XRSession { // Step 4-5 let mut callbacks = mem::replace(&mut *self.raf_callback_list.borrow_mut(), vec![]); + let start = self.global().as_window().get_navigation_start(); + let time = (frame.time_ns - start).to_ms(); let frame = XRFrame::new(&self.global(), self, frame); // Step 6,7 @@ -333,8 +357,10 @@ impl XRSession { self.outside_raf.set(true); frame.set_active(false); - base_layer.swap_buffers(); - self.session.borrow_mut().render_animation_frame(); + if self.is_immersive() { + base_layer.swap_buffers(); + self.session.borrow_mut().render_animation_frame(); + } self.request_new_xr_frame(); // If the canvas element is attached to the DOM, it is now dirty, @@ -345,6 +371,47 @@ impl XRSession { .upcast::<Node>() .dirty(NodeDamage::OtherNodeDamage); } + + 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()); + let size = self + .active_render_state + .get() + .GetBaseLayer() + .expect("Must never construct views when base layer is not set") + .size(); + View { + // Inline views have no offset + transform: RigidTransform3D::identity(), + projection: *self.inline_projection_matrix.borrow(), + viewport: Rect::from_size(size.to_i32()), + } + } } impl XRSessionMethods for XRSession { @@ -394,19 +461,43 @@ impl XRSessionMethods for XRSession { } } - // XXXManishearth step 4: - // If newState’s inlineVerticalFieldOfView is set and session is an - // immersive session, throw an InvalidStateError and abort these steps. + // Step 4: + if init.inlineVerticalFieldOfView.is_some() && self.is_immersive() { + return Err(Error::InvalidState); + } let pending = self .pending_render_state .or_init(|| self.active_render_state.get().clone_object()); if let Some(near) = init.depthNear { - pending.set_depth_near(*near); + 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_layer(Some(&layer)) } @@ -416,7 +507,6 @@ impl XRSessionMethods for XRSession { .borrow_mut() .update_clip_planes(*pending.DepthNear() as f32, *pending.DepthFar() as f32); } - // XXXManishearth handle inlineVerticalFieldOfView Ok(()) } @@ -481,6 +571,19 @@ impl XRSessionMethods for XRSession { 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 diff --git a/components/script/dom/xrspace.rs b/components/script/dom/xrspace.rs index 29d81fa6afe..652a4345ce2 100644 --- a/components/script/dom/xrspace.rs +++ b/components/script/dom/xrspace.rs @@ -68,7 +68,7 @@ impl XRSpace { /// with other spaces pub fn get_pose(&self, base_pose: &Frame) -> Option<ApiPose> { if let Some(reference) = self.downcast::<XRReferenceSpace>() { - Some(reference.get_pose(base_pose)) + reference.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 diff --git a/components/script/dom/xrtest.rs b/components/script/dom/xrtest.rs index 05d608b46a4..b00a69b8fca 100644 --- a/components/script/dom/xrtest.rs +++ b/components/script/dom/xrtest.rs @@ -21,18 +21,15 @@ use crate::dom::promise::Promise; use crate::script_thread::ScriptThread; use crate::task_source::TaskSource; use dom_struct::dom_struct; -use euclid::RigidTransform3D; 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::{self, Error as XRError, MockDeviceInit, MockDeviceMsg}; #[dom_struct] pub struct XRTest { reflector: Reflector, - session_started: Cell<bool>, devices_connected: DomRefCell<Vec<Dom<FakeXRDevice>>>, } @@ -40,7 +37,6 @@ impl XRTest { pub fn new_inherited() -> XRTest { XRTest { reflector: Reflector::new(), - session_started: Cell::new(false), devices_connected: DomRefCell::new(vec![]), } } @@ -76,33 +72,28 @@ impl XRTestMethods for XRTest { fn SimulateDeviceConnection(&self, init: &FakeXRDeviceInit) -> Rc<Promise> { let p = Promise::new(&self.global()); - if !init.supportsImmersive || self.session_started.get() { - p.reject_native(&()); - return p; - } - let origin = if let Some(ref o) = init.viewerOrigin { match get_origin(&o) { - Ok(origin) => origin, + Ok(origin) => Some(origin), Err(e) => { p.reject_error(e); return p; }, } } else { - RigidTransform3D::identity() + None }; let floor_origin = if let Some(ref o) = init.floorOrigin { match get_origin(&o) { - Ok(origin) => origin, + Ok(origin) => Some(origin), Err(e) => { p.reject_error(e); return p; }, } } else { - RigidTransform3D::identity() + None }; let views = match get_views(&init.views) { @@ -121,8 +112,6 @@ impl XRTestMethods for XRTest { floor_origin, }; - self.session_started.set(true); - let global = self.global(); let window = global.as_window(); let this = Trusted::new(self); diff --git a/components/script/dom/xrviewerpose.rs b/components/script/dom/xrviewerpose.rs index 8ebabc0781f..0e64613294b 100644 --- a/components/script/dom/xrviewerpose.rs +++ b/components/script/dom/xrviewerpose.rs @@ -42,6 +42,13 @@ impl XRViewerPose { ) -> DomRoot<XRViewerPose> { rooted_vec!(let mut views); session.with_session(|s| match s.views() { + Views::Inline => views.push(XRView::new( + global, + session, + &session.inline_view(), + XREye::None, + &pose, + )), Views::Mono(view) => { views.push(XRView::new(global, session, &view, XREye::None, &pose)) }, diff --git a/components/script/dom/xrwebgllayer.rs b/components/script/dom/xrwebgllayer.rs index 44bc10868b6..498a5035ce0 100644 --- a/components/script/dom/xrwebgllayer.rs +++ b/components/script/dom/xrwebgllayer.rs @@ -20,9 +20,10 @@ use crate::dom::xrview::XRView; use crate::dom::xrviewport::XRViewport; use canvas_traits::webgl::WebGLFramebufferId; use dom_struct::dom_struct; +use euclid::{Point2D, Rect, Size2D}; use std::convert::TryInto; use webxr_api::SwapChainId as WebXRSwapChainId; -use webxr_api::Views; +use webxr_api::{Viewport, Views}; #[dom_struct] pub struct XRWebGLLayer { @@ -32,19 +33,20 @@ pub struct XRWebGLLayer { stencil: bool, alpha: bool, #[ignore_malloc_size_of = "ids don't malloc"] - swap_chain_id: WebXRSwapChainId, + swap_chain_id: Option<WebXRSwapChainId>, context: Dom<WebGLRenderingContext>, session: Dom<XRSession>, - framebuffer: Dom<WebGLFramebuffer>, + /// If none, this is an inline session (the composition disabled flag is true) + framebuffer: Option<Dom<WebGLFramebuffer>>, } impl XRWebGLLayer { pub fn new_inherited( - swap_chain_id: WebXRSwapChainId, + swap_chain_id: Option<WebXRSwapChainId>, session: &XRSession, context: &WebGLRenderingContext, init: &XRWebGLLayerInit, - framebuffer: &WebGLFramebuffer, + framebuffer: Option<&WebGLFramebuffer>, ) -> XRWebGLLayer { XRWebGLLayer { reflector_: Reflector::new(), @@ -55,17 +57,17 @@ impl XRWebGLLayer { swap_chain_id, context: Dom::from_ref(context), session: Dom::from_ref(session), - framebuffer: Dom::from_ref(framebuffer), + framebuffer: framebuffer.map(Dom::from_ref), } } pub fn new( global: &GlobalScope, - swap_chain_id: WebXRSwapChainId, + swap_chain_id: Option<WebXRSwapChainId>, session: &XRSession, context: &WebGLRenderingContext, init: &XRWebGLLayerInit, - framebuffer: &WebGLFramebuffer, + framebuffer: Option<&WebGLFramebuffer>, ) -> DomRoot<XRWebGLLayer> { reflect_dom_object( Box::new(XRWebGLLayer::new_inherited( @@ -87,6 +89,7 @@ impl XRWebGLLayer { context: &WebGLRenderingContext, init: &XRWebGLLayerInit, ) -> Fallible<DomRoot<Self>> { + let framebuffer; // Step 2 if session.is_ended() { return Err(Error::InvalidState); @@ -95,9 +98,15 @@ impl XRWebGLLayer { // XXXManishearth step 4: check XR compat flag for immersive sessions // 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()); - let (swap_chain_id, framebuffer) = - WebGLFramebuffer::maybe_new_webxr(session, context, size).ok_or(Error::Operation)?; + let (swap_chain_id, framebuffer) = if session.is_immersive() { + let size = session.with_session(|session| session.recommended_framebuffer_resolution()); + let (swap_chain_id, fb) = WebGLFramebuffer::maybe_new_webxr(session, context, size) + .ok_or(Error::Operation)?; + framebuffer = fb; + (Some(swap_chain_id), Some(&*framebuffer)) + } else { + (None, None) + }; // 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." @@ -115,12 +124,13 @@ impl XRWebGLLayer { session, context, init, - &framebuffer, + framebuffer, )) } pub fn swap_chain_id(&self) -> WebXRSwapChainId { self.swap_chain_id + .expect("swap_chain_id must not be called for inline sessions") } pub fn session(&self) -> &XRSession { @@ -128,10 +138,27 @@ impl XRWebGLLayer { } pub fn swap_buffers(&self) { - if let WebGLFramebufferId::Opaque(id) = self.framebuffer.id() { + if let WebGLFramebufferId::Opaque(id) = self + .framebuffer + .as_ref() + .expect("swap_buffers must not be called for inline sessions") + .id() + { self.context.swap_buffers(Some(id)); } } + + 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 { + Size2D::from_untyped(self.context.Canvas().get_size()) + } + } } impl XRWebGLLayerMethods for XRWebGLLayer { @@ -161,28 +188,18 @@ impl XRWebGLLayerMethods for XRWebGLLayer { } /// https://immersive-web.github.io/webxr/#dom-xrwebgllayer-framebuffer - fn Framebuffer(&self) -> DomRoot<WebGLFramebuffer> { - DomRoot::from_ref(&self.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.framebuffer - .size() - .unwrap_or((0, 0)) - .0 - .try_into() - .unwrap_or(0) + self.size().width } /// https://immersive-web.github.io/webxr/#dom-xrwebgllayer-framebufferheight fn FramebufferHeight(&self) -> u32 { - self.framebuffer - .size() - .unwrap_or((0, 0)) - .1 - .try_into() - .unwrap_or(0) + self.size().height } /// https://immersive-web.github.io/webxr/#dom-xrwebgllayer-getviewport @@ -194,11 +211,13 @@ impl XRWebGLLayerMethods for XRWebGLLayer { let views = self.session.with_session(|s| s.views().clone()); let viewport = match (view.Eye(), views) { + (XREye::None, Views::Inline) => { + let origin = Point2D::new(0, 0); + Rect::new(origin, self.size().cast()) + }, (XREye::None, Views::Mono(view)) => view.viewport, (XREye::Left, Views::Stereo(view, _)) => view.viewport, (XREye::Right, Views::Stereo(_, view)) => view.viewport, - // The spec doesn't really say what to do in this case - // https://github.com/immersive-web/webxr/issues/769 _ => return None, }; |