mirror of
https://github.com/servo/servo.git
synced 2025-07-22 23:03:42 +01:00
Barebones media UI
This commit is contained in:
parent
4f6b86f9f5
commit
1c02fc94a8
11 changed files with 177 additions and 25 deletions
|
@ -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();
|
||||
|
|
|
@ -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>) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -211,3 +211,9 @@ partial interface Document {
|
|||
};
|
||||
|
||||
Document implements DocumentOrShadowRoot;
|
||||
|
||||
// Servo internal API.
|
||||
partial interface Document {
|
||||
[Throws]
|
||||
ShadowRoot servoGetMediaControls(DOMString id);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -702,6 +702,9 @@ impl ResourceReaderMethods for ResourceReaderInstance {
|
|||
Resource::BluetoothBlocklist => {
|
||||
&include_bytes!("../../../../resources/gatt_blocklist.txt")[..]
|
||||
},
|
||||
Resource::MediaControls => {
|
||||
&include_bytes!("../../../../resources/media_controls.js")[..]
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
23
resources/media_controls.js
Normal file
23
resources/media_controls.js
Normal 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);
|
Loading…
Add table
Add a link
Reference in a new issue