aboutsummaryrefslogtreecommitdiffstats
path: root/components/script
diff options
context:
space:
mode:
Diffstat (limited to 'components/script')
-rw-r--r--components/script/dom/document.rs36
-rw-r--r--components/script/dom/element.rs22
-rw-r--r--components/script/dom/htmlmediaelement.rs109
-rw-r--r--components/script/dom/node.rs65
-rw-r--r--components/script/dom/shadowroot.rs35
-rw-r--r--components/script/dom/webidls/Document.webidl6
-rw-r--r--components/script/dom/webidls/HTMLMediaElement.webidl2
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;