diff options
Diffstat (limited to 'components/script')
-rw-r--r-- | components/script/dom/document.rs | 36 | ||||
-rw-r--r-- | components/script/dom/element.rs | 22 | ||||
-rw-r--r-- | components/script/dom/htmlmediaelement.rs | 109 | ||||
-rw-r--r-- | components/script/dom/node.rs | 65 | ||||
-rw-r--r-- | components/script/dom/shadowroot.rs | 35 | ||||
-rw-r--r-- | components/script/dom/webidls/Document.webidl | 6 | ||||
-rw-r--r-- | components/script/dom/webidls/HTMLMediaElement.webidl | 2 |
7 files changed, 217 insertions, 58 deletions
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 1122fa52552..5b17c8ea004 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -20,6 +20,7 @@ use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFram use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use crate::dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilter; use crate::dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceMethods; +use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootMethods; use crate::dom::bindings::codegen::Bindings::TouchBinding::TouchMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::{ FrameRequestCallback, ScrollBehavior, WindowMethods, @@ -163,6 +164,7 @@ use style::stylesheet_set::DocumentStylesheetSet; use style::stylesheets::{Origin, OriginSet, Stylesheet}; use url::percent_encoding::percent_decode; use url::Host; +use uuid::Uuid; /// The number of times we are allowed to see spurious `requestAnimationFrame()` calls before /// falling back to fake ones. @@ -385,6 +387,12 @@ pub struct Document { shadow_roots: DomRefCell<HashSet<Dom<ShadowRoot>>>, /// Whether any of the shadow roots need the stylesheets flushed. shadow_roots_styles_changed: Cell<bool>, + /// List of registered media controls. + /// We need to keep this list to allow the media controls to + /// access the "privileged" document.servoGetMediaControls(id) API, + /// where `id` needs to match any of the registered ShadowRoots + /// hosting the media controls UI. + media_controls: DomRefCell<HashMap<String, Dom<ShadowRoot>>>, } #[derive(JSTraceable, MallocSizeOf)] @@ -2457,6 +2465,23 @@ impl Document { self.responsive_images.borrow_mut().remove(i); } } + + pub fn register_media_controls(&self, controls: &ShadowRoot) -> String { + let id = Uuid::new_v4().to_string(); + self.media_controls + .borrow_mut() + .insert(id.clone(), Dom::from_ref(controls)); + id + } + + pub fn unregister_media_controls(&self, id: &str) { + if let Some(ref media_controls) = self.media_controls.borrow_mut().remove(id) { + let media_controls = DomRoot::from_ref(&**media_controls); + media_controls.Host().detach_shadow(); + } else { + debug_assert!(false, "Trying to unregister unknown media controls"); + } + } } #[derive(MallocSizeOf, PartialEq)] @@ -2750,6 +2775,7 @@ impl Document { delayed_tasks: Default::default(), shadow_roots: DomRefCell::new(HashSet::new()), shadow_roots_styles_changed: Cell::new(false), + media_controls: DomRefCell::new(HashMap::new()), } } @@ -4551,6 +4577,16 @@ impl DocumentMethods for Document { fn ExitFullscreen(&self) -> Rc<Promise> { self.exit_fullscreen() } + + // check-tidy: no specs after this line + // Servo only API to get an instance of the controls of a specific + // media element matching the given id. + fn ServoGetMediaControls(&self, id: DOMString) -> Fallible<DomRoot<ShadowRoot>> { + match self.media_controls.borrow().get(&*id) { + Some(m) => Ok(DomRoot::from_ref(&*m)), + None => Err(Error::InvalidAccess), + } + } } fn update_with_current_time_ms(marker: &Cell<u64>) { diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 1145cb0fd7e..91d7a8aa450 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -78,7 +78,7 @@ use crate::dom::nodelist::NodeList; use crate::dom::promise::Promise; use crate::dom::raredata::ElementRareData; use crate::dom::servoparser::ServoParser; -use crate::dom::shadowroot::ShadowRoot; +use crate::dom::shadowroot::{IsUserAgentWidget, ShadowRoot}; use crate::dom::text::Text; use crate::dom::validation::Validatable; use crate::dom::virtualmethods::{vtable_for, VirtualMethods}; @@ -231,13 +231,6 @@ impl FromStr for AdjacentPosition { } } -/// Whether a shadow root hosts an User Agent widget. -#[derive(PartialEq)] -pub enum IsUserAgentWidget { - No, - Yes, -} - // // Element methods // @@ -498,14 +491,25 @@ impl Element { self.ensure_rare_data().shadow_root = Some(Dom::from_ref(&*shadow_root)); shadow_root .upcast::<Node>() - .set_containing_shadow_root(&shadow_root); + .set_containing_shadow_root(Some(&shadow_root)); if self.is_connected() { self.node.owner_doc().register_shadow_root(&*shadow_root); } + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + Ok(shadow_root) } + + pub fn detach_shadow(&self) { + if let Some(ref shadow_root) = self.shadow_root() { + shadow_root.detach(); + self.ensure_rare_data().shadow_root = None; + } else { + debug_assert!(false, "Trying to detach a non-attached shadow root"); + } + } } #[allow(unsafe_code)] diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index 9ba07fe0c09..1b6f8ab1e3c 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -15,6 +15,7 @@ use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaE use crate::dom::bindings::codegen::Bindings::HTMLSourceElementBinding::HTMLSourceElementMethods; use crate::dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorConstants::*; use crate::dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods; use crate::dom::bindings::codegen::Bindings::TextTrackBinding::{TextTrackKind, TextTrackMode}; use crate::dom::bindings::codegen::InheritTypes::{ElementTypeId, HTMLElementTypeId}; use crate::dom::bindings::codegen::InheritTypes::{HTMLMediaElementTypeId, NodeTypeId}; @@ -33,18 +34,21 @@ use crate::dom::document::Document; use crate::dom::element::{ cors_setting_for_element, reflect_cross_origin_attribute, set_cross_origin_attribute, }; -use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::element::{AttributeMutation, Element, ElementCreator}; use crate::dom::event::Event; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlscriptelement::HTMLScriptElement; use crate::dom::htmlsourceelement::HTMLSourceElement; +use crate::dom::htmlstyleelement::HTMLStyleElement; use crate::dom::htmlvideoelement::HTMLVideoElement; use crate::dom::mediaerror::MediaError; use crate::dom::mediastream::MediaStream; use crate::dom::node::{document_from_node, window_from_node, Node, NodeDamage, UnbindContext}; use crate::dom::performanceresourcetiming::InitiatorType; use crate::dom::promise::Promise; +use crate::dom::shadowroot::IsUserAgentWidget; use crate::dom::texttrack::TextTrack; use crate::dom::texttracklist::TextTrackList; use crate::dom::timeranges::{TimeRanges, TimeRangesContainer}; @@ -59,6 +63,7 @@ use crate::network_listener::{self, NetworkListener, PreInvoke, ResourceTimingLi use crate::script_thread::ScriptThread; use crate::task_source::TaskSource; use dom_struct::dom_struct; +use embedder_traits::resources::{self, Resource as EmbedderResource}; use euclid::Size2D; use headers::{ContentLength, ContentRange, HeaderMapExt}; use html5ever::{LocalName, Prefix}; @@ -335,6 +340,11 @@ pub struct HTMLMediaElement { current_fetch_context: DomRefCell<Option<HTMLMediaElementFetchContext>>, /// Player Id reported the player thread id: Cell<u64>, + /// Media controls id. + /// In order to workaround the lack of privileged JS context, we secure the + /// the access to the "privileged" document.servoGetMediaControls(id) API by + /// keeping a whitelist of media controls identifiers. + media_controls_id: DomRefCell<Option<String>>, } /// <https://html.spec.whatwg.org/multipage/#dom-media-networkstate> @@ -397,6 +407,7 @@ impl HTMLMediaElement { next_timeupdate_event: Cell::new(time::get_time() + Duration::milliseconds(250)), current_fetch_context: DomRefCell::new(None), id: Cell::new(0), + media_controls_id: DomRefCell::new(None), } } @@ -1637,6 +1648,12 @@ impl HTMLMediaElement { // https://github.com/servo/media/issues/156 // Step 12 & 13 are already handled by the earlier media track processing. + + // We wait until we have metadata to render the controls, so we render them + // with the appropriate size. + if self.Controls() { + self.render_controls(); + } }, PlayerEvent::NeedData => { // The player needs more data. @@ -1712,6 +1729,68 @@ impl HTMLMediaElement { .start(0) .unwrap_or_else(|_| self.playback_position.get()) } + + fn render_controls(&self) { + let element = self.htmlelement.upcast::<Element>(); + if self.ready_state.get() < ReadyState::HaveMetadata || element.is_shadow_host() { + // Bail out if we have no metadata yet or + // if we are already showing the controls. + return; + } + let shadow_root = element.attach_shadow(IsUserAgentWidget::Yes).unwrap(); + let document = document_from_node(self); + let script = HTMLScriptElement::new( + local_name!("script"), + None, + &document, + ElementCreator::ScriptCreated, + ); + let mut media_controls_script = resources::read_string(EmbedderResource::MediaControlsJS); + // This is our hacky way to temporarily workaround the lack of a privileged + // JS context. + // The media controls UI accesses the document.servoGetMediaControls(id) API + // to get an instance to the media controls ShadowRoot. + // `id` needs to match the internally generated UUID assigned to a media element. + let id = document.register_media_controls(&shadow_root); + let media_controls_script = media_controls_script.as_mut_str().replace("@@@id@@@", &id); + *self.media_controls_id.borrow_mut() = Some(id); + script + .upcast::<Node>() + .SetTextContent(Some(DOMString::from(media_controls_script))); + if let Err(e) = shadow_root + .upcast::<Node>() + .AppendChild(&*script.upcast::<Node>()) + { + warn!("Could not render media controls {:?}", e); + return; + } + + let media_controls_style = resources::read_string(EmbedderResource::MediaControlsCSS); + let style = HTMLStyleElement::new( + local_name!("script"), + None, + &document, + ElementCreator::ScriptCreated, + ); + style + .upcast::<Node>() + .SetTextContent(Some(DOMString::from(media_controls_style))); + + if let Err(e) = shadow_root + .upcast::<Node>() + .AppendChild(&*style.upcast::<Node>()) + { + warn!("Could not render media controls {:?}", e); + } + + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + } + + fn remove_controls(&self) { + if let Some(id) = self.media_controls_id.borrow_mut().take() { + document_from_node(self).unregister_media_controls(&id); + } + } } // XXX Placeholder for [https://github.com/servo/servo/issues/22293] @@ -1754,6 +1833,7 @@ impl Drop for HTMLMediaElement { .unwrap() .shutdown_player(&client_context_id, player.clone()); } + self.remove_controls(); } } @@ -1783,6 +1863,11 @@ impl HTMLMediaElementMethods for HTMLMediaElement { // https://html.spec.whatwg.org/multipage/#dom-media-defaultmuted make_bool_setter!(SetDefaultMuted, "muted"); + // https://html.spec.whatwg.org/multipage/#dom-media-controls + make_bool_getter!(Controls, "controls"); + // https://html.spec.whatwg.org/multipage/#dom-media-controls + make_bool_setter!(SetControls, "controls"); + // https://html.spec.whatwg.org/multipage/#dom-media-src make_url_getter!(Src, "src"); @@ -2177,19 +2262,23 @@ impl VirtualMethods for HTMLMediaElement { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { self.super_type().unwrap().attribute_mutated(attr, mutation); - if &local_name!("muted") == attr.local_name() { - self.SetMuted(mutation.new_value(attr).is_some()); - return; - } - - if mutation.new_value(attr).is_none() { - return; - } - match attr.local_name() { + &local_name!("muted") => { + self.SetMuted(mutation.new_value(attr).is_some()); + }, &local_name!("src") => { + if mutation.new_value(attr).is_none() { + return; + } self.media_element_load_algorithm(); }, + &local_name!("controls") => { + if mutation.new_value(attr).is_some() { + self.render_controls(); + } else { + self.remove_controls(); + } + }, _ => (), }; } diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index fe7f9ec29c2..8165d0c4eef 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -283,7 +283,7 @@ impl Node { for node in new_child.traverse_preorder(ShadowIncluding::No) { if parent_in_shadow_tree { if let Some(shadow_root) = self.containing_shadow_root() { - node.set_containing_shadow_root(&*shadow_root); + node.set_containing_shadow_root(Some(&*shadow_root)); } debug_assert!(node.containing_shadow_root().is_some()); } @@ -299,6 +299,36 @@ impl Node { } } + /// Clean up flags and unbind from tree. + pub fn complete_remove_subtree(root: &Node, context: &UnbindContext) { + for node in root.traverse_preorder(ShadowIncluding::Yes) { + // Out-of-document elements never have the descendants flag set. + node.set_flag( + NodeFlags::IS_IN_DOC | + NodeFlags::IS_CONNECTED | + NodeFlags::HAS_DIRTY_DESCENDANTS | + NodeFlags::HAS_SNAPSHOT | + NodeFlags::HANDLED_SNAPSHOT, + false, + ); + } + for node in root.traverse_preorder(ShadowIncluding::Yes) { + // This needs to be in its own loop, because unbind_from_tree may + // rely on the state of IS_IN_DOC of the context node's descendants, + // e.g. when removing a <form>. + vtable_for(&&*node).unbind_from_tree(&context); + node.style_and_layout_data.get().map(|d| node.dispose(d)); + // https://dom.spec.whatwg.org/#concept-node-remove step 14 + if let Some(element) = node.as_custom_element() { + ScriptThread::enqueue_callback_reaction( + &*element, + CallbackReaction::Disconnected, + None, + ); + } + } + } + /// Removes the given child from this node's list of children. /// /// Fails unless `child` is a child of this node. @@ -339,32 +369,7 @@ impl Node { child.parent_node.set(None); self.children_count.set(self.children_count.get() - 1); - for node in child.traverse_preorder(ShadowIncluding::Yes) { - // Out-of-document elements never have the descendants flag set. - node.set_flag( - NodeFlags::IS_IN_DOC | - NodeFlags::IS_CONNECTED | - NodeFlags::HAS_DIRTY_DESCENDANTS | - NodeFlags::HAS_SNAPSHOT | - NodeFlags::HANDLED_SNAPSHOT, - false, - ); - } - for node in child.traverse_preorder(ShadowIncluding::Yes) { - // This needs to be in its own loop, because unbind_from_tree may - // rely on the state of IS_IN_DOC of the context node's descendants, - // e.g. when removing a <form>. - vtable_for(&&*node).unbind_from_tree(&context); - node.style_and_layout_data.get().map(|d| node.dispose(d)); - // https://dom.spec.whatwg.org/#concept-node-remove step 14 - if let Some(element) = node.as_custom_element() { - ScriptThread::enqueue_callback_reaction( - &*element, - CallbackReaction::Disconnected, - None, - ); - } - } + Self::complete_remove_subtree(child, &context); } pub fn to_untrusted_node_address(&self) -> UntrustedNodeAddress { @@ -961,8 +966,8 @@ impl Node { .map(|sr| DomRoot::from_ref(&**sr)) } - pub fn set_containing_shadow_root(&self, shadow_root: &ShadowRoot) { - self.ensure_rare_data().containing_shadow_root = Some(Dom::from_ref(shadow_root)); + pub fn set_containing_shadow_root(&self, shadow_root: Option<&ShadowRoot>) { + self.ensure_rare_data().containing_shadow_root = shadow_root.map(Dom::from_ref); } pub fn is_in_html_doc(&self) -> bool { @@ -3082,7 +3087,7 @@ pub struct UnbindContext<'a> { impl<'a> UnbindContext<'a> { /// Create a new `UnbindContext` value. - fn new( + pub fn new( parent: &'a Node, prev_sibling: Option<&'a Node>, next_sibling: Option<&'a Node>, diff --git a/components/script/dom/shadowroot.rs b/components/script/dom/shadowroot.rs index ae108f8109c..d6df87bded8 100644 --- a/components/script/dom/shadowroot.rs +++ b/components/script/dom/shadowroot.rs @@ -14,7 +14,7 @@ use crate::dom::document::Document; use crate::dom::documentfragment::DocumentFragment; use crate::dom::documentorshadowroot::{DocumentOrShadowRoot, StyleSheetInDocument}; use crate::dom::element::Element; -use crate::dom::node::{Node, NodeDamage, NodeFlags, ShadowIncluding}; +use crate::dom::node::{Node, NodeDamage, NodeFlags, ShadowIncluding, UnbindContext}; use crate::dom::stylesheetlist::{StyleSheetList, StyleSheetListOwner}; use crate::dom::window::Window; use crate::stylesheet_set::StylesheetSetRef; @@ -28,13 +28,20 @@ use style::media_queries::Device; use style::shared_lock::SharedRwLockReadGuard; use style::stylesheets::Stylesheet; +/// Whether a shadow root hosts an User Agent widget. +#[derive(JSTraceable, MallocSizeOf, PartialEq)] +pub enum IsUserAgentWidget { + No, + Yes, +} + // https://dom.spec.whatwg.org/#interface-shadowroot #[dom_struct] pub struct ShadowRoot { document_fragment: DocumentFragment, document_or_shadow_root: DocumentOrShadowRoot, document: Dom<Document>, - host: Dom<Element>, + host: MutNullableDom<Element>, /// List of author styles associated with nodes in this shadow tree. author_styles: DomRefCell<AuthorStyles<StyleSheetInDocument>>, stylesheet_list: MutNullableDom<StyleSheetList>, @@ -55,7 +62,7 @@ impl ShadowRoot { document_fragment, document_or_shadow_root: DocumentOrShadowRoot::new(document.window()), document: Dom::from_ref(document), - host: Dom::from_ref(host), + host: MutNullableDom::new(Some(host)), author_styles: DomRefCell::new(AuthorStyles::new()), stylesheet_list: MutNullableDom::new(None), window: Dom::from_ref(document.window()), @@ -70,6 +77,14 @@ impl ShadowRoot { ) } + pub fn detach(&self) { + self.document.unregister_shadow_root(&self); + let node = self.upcast::<Node>(); + node.set_containing_shadow_root(None); + Node::complete_remove_subtree(&node, &UnbindContext::new(node, None, None, None)); + self.host.set(None); + } + pub fn get_focused_element(&self) -> Option<DomRoot<Element>> { //XXX get retargeted focused element None @@ -123,9 +138,9 @@ impl ShadowRoot { self.document.invalidate_shadow_roots_stylesheets(); self.author_styles.borrow_mut().stylesheets.force_dirty(); // Mark the host element dirty so a reflow will be performed. - self.host - .upcast::<Node>() - .dirty(NodeDamage::NodeStyleDamaged); + if let Some(host) = self.host.get() { + host.upcast::<Node>().dirty(NodeDamage::NodeStyleDamaged); + } } /// Remove any existing association between the provided id and any elements @@ -209,7 +224,8 @@ impl ShadowRootMethods for ShadowRoot { /// https://dom.spec.whatwg.org/#dom-shadowroot-host fn Host(&self) -> DomRoot<Element> { - DomRoot::from_ref(&self.host) + let host = self.host.get(); + host.expect("Trying to get host from a detached shadow root") } // https://drafts.csswg.org/cssom/#dom-document-stylesheets @@ -241,7 +257,10 @@ impl LayoutShadowRootHelpers for LayoutDom<ShadowRoot> { #[inline] #[allow(unsafe_code)] unsafe fn get_host_for_layout(&self) -> LayoutDom<Element> { - (*self.unsafe_get()).host.to_layout() + (*self.unsafe_get()) + .host + .get_inner_as_layout() + .expect("We should never do layout on a detached shadow root") } #[inline] diff --git a/components/script/dom/webidls/Document.webidl b/components/script/dom/webidls/Document.webidl index 0127f8ecf98..ebcdea32166 100644 --- a/components/script/dom/webidls/Document.webidl +++ b/components/script/dom/webidls/Document.webidl @@ -211,3 +211,9 @@ partial interface Document { }; Document implements DocumentOrShadowRoot; + +// Servo internal API. +partial interface Document { + [Throws] + ShadowRoot servoGetMediaControls(DOMString id); +}; diff --git a/components/script/dom/webidls/HTMLMediaElement.webidl b/components/script/dom/webidls/HTMLMediaElement.webidl index 21ac720fd06..cdf32c98312 100644 --- a/components/script/dom/webidls/HTMLMediaElement.webidl +++ b/components/script/dom/webidls/HTMLMediaElement.webidl @@ -53,7 +53,7 @@ interface HTMLMediaElement : HTMLElement { void pause(); // controls - // [CEReactions] attribute boolean controls; + [CEReactions] attribute boolean controls; [Throws] attribute double volume; attribute boolean muted; [CEReactions] attribute boolean defaultMuted; |