Barebones media UI

This commit is contained in:
Fernando Jiménez Moreno 2019-04-05 10:50:01 +02:00
parent 4f6b86f9f5
commit 1c02fc94a8
11 changed files with 177 additions and 25 deletions

View file

@ -60,6 +60,7 @@ pub enum Resource {
PresentationalHintsCSS,
QuirksModeCSS,
RippyPNG,
MediaControls,
}
pub trait ResourceReaderMethods {
@ -94,6 +95,7 @@ fn resources_for_tests() -> Box<dyn ResourceReaderMethods + Sync + Send> {
Resource::PresentationalHintsCSS => "presentational-hints.css",
Resource::QuirksModeCSS => "quirks-mode.css",
Resource::RippyPNG => "rippy.png",
Resource::MediaControls => "media_controls.js",
};
let mut path = env::current_exe().unwrap();
path = path.canonicalize().unwrap();

View file

@ -163,6 +163,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 +386,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 +2464,18 @@ 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) {
self.media_controls.borrow_mut().remove(id);
}
}
#[derive(MallocSizeOf, PartialEq)]
@ -2750,6 +2769,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 +4571,15 @@ impl DocumentMethods for Document {
fn ExitFullscreen(&self) -> Rc<Promise> {
self.exit_fullscreen()
}
// 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>) {

View file

@ -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
//
@ -494,7 +487,7 @@ impl Element {
}
// Steps 4, 5 and 6.
let shadow_root = ShadowRoot::new(self, &*self.node.owner_doc());
let shadow_root = ShadowRoot::new(self, &*self.node.owner_doc(), is_ua_widget);
self.ensure_rare_data().shadow_root = Some(Dom::from_ref(&*shadow_root));
shadow_root
.upcast::<Node>()
@ -504,6 +497,8 @@ impl Element {
self.node.owner_doc().register_shadow_root(&*shadow_root);
}
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
Ok(shadow_root)
}
}

View file

@ -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,11 +34,12 @@ 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::htmlvideoelement::HTMLVideoElement;
use crate::dom::mediaerror::MediaError;
@ -45,6 +47,7 @@ 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 +62,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 +339,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 +406,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),
}
}
@ -1712,6 +1722,47 @@ impl HTMLMediaElement {
.start(0)
.unwrap_or_else(|_| self.playback_position.get())
}
fn render_controls(&self) {
println!("render_controls");
// XXX cannot render controls while parsing.
// XXX render controls as a top layer.
// XXX check that controls are not already rendered.
let element = self.htmlelement.upcast::<Element>();
if let Ok(shadow_root) = element.attach_shadow(IsUserAgentWidget::Yes) {
let document = document_from_node(self);
let script = HTMLScriptElement::new(
local_name!("script"),
None,
&document,
ElementCreator::ScriptCreated,
);
let mut media_controls = resources::read_string(EmbedderResource::MediaControls);
// 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 = media_controls.as_mut_str().replace("@@@id@@@", &id);
*self.media_controls_id.borrow_mut() = Some(id);
script
.upcast::<Node>()
.SetTextContent(Some(DOMString::from(media_controls)));
if let Err(e) = shadow_root
.upcast::<Node>()
.AppendChild(&*script.upcast::<Node>())
{
warn!("Could not render media controls {:?}", e);
}
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
}
}
fn hide_controls(&self) {
println!("hide_controls");
}
}
// XXX Placeholder for [https://github.com/servo/servo/issues/22293]
@ -1754,6 +1805,9 @@ impl Drop for HTMLMediaElement {
.unwrap()
.shutdown_player(&client_context_id, player.clone());
}
if let Some(ref id) = *self.media_controls_id.borrow() {
document_from_node(self).unregister_media_controls(id);
}
}
}
@ -2177,19 +2231,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() {
match attr.local_name() {
&local_name!("muted") => {
self.SetMuted(mutation.new_value(attr).is_some());
return;
}
},
&local_name!("src") => {
if mutation.new_value(attr).is_none() {
return;
}
match attr.local_name() {
&local_name!("src") => {
self.media_element_load_algorithm();
},
&local_name!("controls") => {
if mutation.new_value(attr).is_some() {
self.render_controls();
} else {
self.hide_controls();
}
},
_ => (),
};
}

View file

@ -24,6 +24,7 @@ use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use net_traits::ReferrerPolicy;
use servo_arc::Arc;
use servo_url::ServoUrl;
use std::cell::Cell;
use style::media_queries::MediaList;
use style::parser::ParserContext as CssParserContext;
@ -111,10 +112,22 @@ impl HTMLStyleElement {
let mq =
Arc::new(shared_lock.wrap(MediaList::parse(&context, &mut CssParser::new(&mut input))));
let loader = StylesheetLoader::for_element(self.upcast());
let (url, origin) = if let Some(shadow_root) = self
.upcast::<Node>()
.containing_shadow_root() {
if shadow_root.is_user_agent_widget() {
(ServoUrl::parse(&format!("chrome://{:?}", window.get_url().to_string())).unwrap(), Origin::UserAgent)
} else {
(window.get_url(), Origin::Author)
}
} else {
(window.get_url(), Origin::Author)
};
let sheet = Stylesheet::from_str(
&data,
window.get_url(),
Origin::Author,
url,
origin,
mq,
shared_lock,
Some(&loader),

View file

@ -28,6 +28,13 @@ 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 {
@ -39,11 +46,13 @@ pub struct ShadowRoot {
author_styles: DomRefCell<AuthorStyles<StyleSheetInDocument>>,
stylesheet_list: MutNullableDom<StyleSheetList>,
window: Dom<Window>,
/// Whether this ShadowRoot hosts a User Agent widget.
is_widget: IsUserAgentWidget,
}
impl ShadowRoot {
#[allow(unrooted_must_root)]
fn new_inherited(host: &Element, document: &Document) -> ShadowRoot {
fn new_inherited(host: &Element, document: &Document, is_widget: IsUserAgentWidget) -> ShadowRoot {
let document_fragment = DocumentFragment::new_inherited(document);
let node = document_fragment.upcast::<Node>();
node.set_flag(NodeFlags::IS_IN_SHADOW_TREE, true);
@ -59,12 +68,13 @@ impl ShadowRoot {
author_styles: DomRefCell::new(AuthorStyles::new()),
stylesheet_list: MutNullableDom::new(None),
window: Dom::from_ref(document.window()),
is_widget,
}
}
pub fn new(host: &Element, document: &Document) -> DomRoot<ShadowRoot> {
pub fn new(host: &Element, document: &Document, is_widget: IsUserAgentWidget) -> DomRoot<ShadowRoot> {
reflect_dom_object(
Box::new(ShadowRoot::new_inherited(host, document)),
Box::new(ShadowRoot::new_inherited(host, document, is_widget)),
document.window(),
ShadowRootBinding::Wrap,
)
@ -152,6 +162,10 @@ impl ShadowRoot {
root,
);
}
pub fn is_user_agent_widget(&self) -> bool {
self.is_widget == IsUserAgentWidget::Yes
}
}
impl ShadowRootMethods for ShadowRoot {

View file

@ -211,3 +211,9 @@ partial interface Document {
};
Document implements DocumentOrShadowRoot;
// Servo internal API.
partial interface Document {
[Throws]
ShadowRoot servoGetMediaControls(DOMString id);
};

View file

@ -141,7 +141,15 @@ impl FetchResponseListener for StylesheetContext {
// TODO: Get the actual value. http://dev.w3.org/csswg/css-syntax/#environment-encoding
let environment_encoding = UTF_8;
let protocol_encoding_label = metadata.charset.as_ref().map(|s| &**s);
let final_url = metadata.final_url;
let final_url = if let Some(ref shadow_root) = self.shadow_root {
if shadow_root.root().is_user_agent_widget() {
ServoUrl::parse(&format!("chrome://{:?}", metadata.final_url.to_string())).unwrap()
} else {
metadata.final_url
}
} else {
metadata.final_url
};
let win = window_from_node(&*elem);

View file

@ -29,6 +29,7 @@ fn filename(file: Resource) -> &'static str {
Resource::PresentationalHintsCSS => "presentational-hints.css",
Resource::QuirksModeCSS => "quirks-mode.css",
Resource::RippyPNG => "rippy.png",
Resource::MediaControls => "media_controls.js",
}
}

View file

@ -702,6 +702,9 @@ impl ResourceReaderMethods for ResourceReaderInstance {
Resource::BluetoothBlocklist => {
&include_bytes!("../../../../resources/gatt_blocklist.txt")[..]
},
Resource::MediaControls => {
&include_bytes!("../../../../resources/media_controls.js")[..]
},
})
}

View file

@ -0,0 +1,23 @@
console.log('YO');
let controls = document.servoGetMediaControls("@@@id@@@");
let style = document.createElement("style");
style.textContent = `#controls {
-servo-top-layer: top;
display: block;
position: fixed;
left: 0px;
bottom: 0px;
height: 30px;
width: 100%;
background: blue;
}`;
controls.appendChild(style);
let div = document.createElement("div");
div.setAttribute("id", "controls");
let button = document.createElement("button");
button.textContent = "Click me";
div.appendChild(button);
controls.appendChild(div);
console.log('INNER', div.innerHTML);