diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2016-11-02 14:51:12 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-11-02 14:51:12 -0500 |
commit | 6ef46ab9e4ec08d5f5226d41f0cac77c3584bae9 (patch) | |
tree | 1eecad9e272742998500a50421130c9131bbe4b8 /components/script/dom | |
parent | 021cabd3e6242b3537addec32010ee8cee556121 (diff) | |
parent | 138a0480fee8e6f0a338d85160aca49f14e245c9 (diff) | |
download | servo-6ef46ab9e4ec08d5f5226d41f0cac77c3584bae9.tar.gz servo-6ef46ab9e4ec08d5f5226d41f0cac77c3584bae9.zip |
Auto merge of #13453 - metajack:media-query-list, r=jdm
Implement matchMedia and MediaQueryList
<!-- Please describe your changes on the following line: -->
---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [ ] These changes fix #13376 (github issue number if applicable).
<!-- Either: -->
- [x] There are tests for these changes OR
- [ ] These changes do not require tests because _____
<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
Fixes #13376.
<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/13453)
<!-- Reviewable:end -->
Diffstat (limited to 'components/script/dom')
-rw-r--r-- | components/script/dom/bindings/codegen/Bindings.conf | 6 | ||||
-rw-r--r-- | components/script/dom/bindings/trace.rs | 3 | ||||
-rw-r--r-- | components/script/dom/mediaquerylist.rs | 156 | ||||
-rw-r--r-- | components/script/dom/mod.rs | 1 | ||||
-rw-r--r-- | components/script/dom/webidls/MediaQueryList.webidl | 14 | ||||
-rw-r--r-- | components/script/dom/webidls/Window.webidl | 2 | ||||
-rw-r--r-- | components/script/dom/window.rs | 25 |
7 files changed, 204 insertions, 3 deletions
diff --git a/components/script/dom/bindings/codegen/Bindings.conf b/components/script/dom/bindings/codegen/Bindings.conf index 434450d2bcc..5647679f446 100644 --- a/components/script/dom/bindings/codegen/Bindings.conf +++ b/components/script/dom/bindings/codegen/Bindings.conf @@ -14,12 +14,16 @@ DOMInterfaces = { +'MediaQueryList': { + 'weakReferenceable': True, +}, + 'Promise': { 'spiderMonkeyInterface': True, }, 'Range': { - 'weakReferenceable': True, + 'weakReferenceable': True, }, #FIXME(jdm): This should be 'register': False, but then we don't generate enum types diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 9ac91f7078c..e0f65d9f6e1 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -91,6 +91,7 @@ use std::time::{SystemTime, Instant}; use string_cache::{Atom, Namespace, QualName}; use style::attr::{AttrIdentifier, AttrValue, LengthOrPercentageOrAuto}; use style::element_state::*; +use style::media_queries::MediaQueryList; use style::properties::PropertyDeclarationBlock; use style::selector_impl::{ElementSnapshot, PseudoElement}; use style::values::specified::Length; @@ -367,7 +368,7 @@ no_jsmanaged_fields!(WebGLProgramId); no_jsmanaged_fields!(WebGLRenderbufferId); no_jsmanaged_fields!(WebGLShaderId); no_jsmanaged_fields!(WebGLTextureId); - +no_jsmanaged_fields!(MediaQueryList); impl JSTraceable for Box<ScriptChan + Send> { #[inline] diff --git a/components/script/dom/mediaquerylist.rs b/components/script/dom/mediaquerylist.rs new file mode 100644 index 00000000000..30e510171b4 --- /dev/null +++ b/components/script/dom/mediaquerylist.rs @@ -0,0 +1,156 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +use cssparser::ToCss; +use dom::bindings::cell::DOMRefCell; +use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; +use dom::bindings::codegen::Bindings::EventListenerBinding::EventListener; +use dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods; +use dom::bindings::codegen::Bindings::MediaQueryListBinding::{self, MediaQueryListMethods}; +use dom::bindings::inheritance::Castable; +use dom::bindings::js::{JS, Root}; +use dom::bindings::reflector::reflect_dom_object; +use dom::bindings::str::DOMString; +use dom::bindings::trace::JSTraceable; +use dom::bindings::weakref::{WeakRef, WeakRefVec}; +use dom::document::Document; +use dom::eventtarget::EventTarget; +use euclid::scale_factor::ScaleFactor; +use js::jsapi::JSTracer; +use std::cell::Cell; +use std::rc::Rc; +use style; +use style::media_queries::{Device, MediaType}; +use style_traits::{PagePx, ViewportPx}; + +pub enum MediaQueryListMatchState { + Same(bool), + Changed(bool), +} + +#[dom_struct] +pub struct MediaQueryList { + eventtarget: EventTarget, + document: JS<Document>, + media_query_list: style::media_queries::MediaQueryList, + last_match_state: Cell<Option<bool>> +} + +impl MediaQueryList { + fn new_inherited(document: &Document, + media_query_list: style::media_queries::MediaQueryList) -> MediaQueryList { + MediaQueryList { + eventtarget: EventTarget::new_inherited(), + document: JS::from_ref(document), + media_query_list: media_query_list, + last_match_state: Cell::new(None), + } + } + + pub fn new(document: &Document, + media_query_list: style::media_queries::MediaQueryList) -> Root<MediaQueryList> { + reflect_dom_object(box MediaQueryList::new_inherited(document, media_query_list), + document.window(), + MediaQueryListBinding::Wrap) + } +} + +impl MediaQueryList { + fn evaluate_changes(&self) -> MediaQueryListMatchState { + let matches = self.evaluate(); + + let result = if let Some(old_matches) = self.last_match_state.get() { + if old_matches == matches { + MediaQueryListMatchState::Same(matches) + } else { + MediaQueryListMatchState::Changed(matches) + } + } else { + MediaQueryListMatchState::Changed(matches) + }; + + self.last_match_state.set(Some(matches)); + result + } + + pub fn evaluate(&self) -> bool { + if let Some(window_size) = self.document.window().window_size() { + let viewport_size = window_size.visible_viewport; + // TODO: support real ViewportPx, including zoom level + // This information seems not to be tracked currently, so we assume + // ViewportPx == PagePx + let page_to_viewport: ScaleFactor<f32, PagePx, ViewportPx> = ScaleFactor::new(1.0); + let device = Device::new(MediaType::Screen, viewport_size * page_to_viewport); + self.media_query_list.evaluate(&device) + } else { + false + } + } +} + +impl MediaQueryListMethods for MediaQueryList { + // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-media + fn Media(&self) -> DOMString { + let mut s = String::new(); + self.media_query_list.to_css(&mut s).unwrap(); + DOMString::from_string(s) + } + + // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-matches + fn Matches(&self) -> bool { + match self.last_match_state.get() { + None => self.evaluate(), + Some(state) => state, + } + } + + // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-addlistener + fn AddListener(&self, listener: Option<Rc<EventListener>>) { + self.upcast::<EventTarget>().AddEventListener(DOMString::from_string("change".to_owned()), + listener, false); + } + + // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-removelistener + fn RemoveListener(&self, listener: Option<Rc<EventListener>>) { + self.upcast::<EventTarget>().RemoveEventListener(DOMString::from_string("change".to_owned()), + listener, false); + } + + // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-onchange + event_handler!(change, GetOnchange, SetOnchange); +} + +#[derive(HeapSizeOf)] +pub struct WeakMediaQueryListVec { + cell: DOMRefCell<WeakRefVec<MediaQueryList>>, +} + +#[allow(unsafe_code)] +impl WeakMediaQueryListVec { + /// Create a new vector of weak references to MediaQueryList + pub fn new() -> Self { + WeakMediaQueryListVec { cell: DOMRefCell::new(WeakRefVec::new()) } + } + + pub fn push(&self, mql: &MediaQueryList) { + self.cell.borrow_mut().push(WeakRef::new(mql)); + } + + /// Evaluate media query lists and report changes + /// https://drafts.csswg.org/cssom-view/#evaluate-media-queries-and-report-changes + pub fn evaluate_and_report_changes(&self) { + for mql in self.cell.borrow().iter() { + if let MediaQueryListMatchState::Changed(_) = mql.root().unwrap().evaluate_changes() { + mql.root().unwrap().upcast::<EventTarget>().fire_simple_event("change"); + } + } + } +} + +#[allow(unsafe_code)] +impl JSTraceable for WeakMediaQueryListVec { + fn trace(&self, _: *mut JSTracer) { + self.cell.borrow_mut().retain_alive() + } +} diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 4c6c7c941c4..14498823424 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -357,6 +357,7 @@ pub mod imagedata; pub mod keyboardevent; pub mod location; pub mod mediaerror; +pub mod mediaquerylist; pub mod messageevent; pub mod mimetype; pub mod mimetypearray; diff --git a/components/script/dom/webidls/MediaQueryList.webidl b/components/script/dom/webidls/MediaQueryList.webidl new file mode 100644 index 00000000000..2991efa89a8 --- /dev/null +++ b/components/script/dom/webidls/MediaQueryList.webidl @@ -0,0 +1,14 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +// https://drafts.csswg.org/cssom-view/#mediaquerylist + +[Exposed=(Window)] +interface MediaQueryList : EventTarget { + readonly attribute DOMString media; + readonly attribute boolean matches; + void addListener(EventListener? listener); + void removeListener(EventListener? listener); + attribute EventHandler onchange; +}; diff --git a/components/script/dom/webidls/Window.webidl b/components/script/dom/webidls/Window.webidl index 87cc3f3e486..ad3494b6f5f 100644 --- a/components/script/dom/webidls/Window.webidl +++ b/components/script/dom/webidls/Window.webidl @@ -120,7 +120,7 @@ dictionary ScrollToOptions : ScrollOptions { // http://dev.w3.org/csswg/cssom-view/#extensions-to-the-window-interface partial interface Window { - //MediaQueryList matchMedia(DOMString query); + [Exposed=(Window), NewObject] MediaQueryList matchMedia(DOMString query); [SameObject] readonly attribute Screen screen; // browsing context diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 5afbf475cc9..099e50f4820 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use app_units::Au; +use cssparser::Parser; use devtools_traits::{ScriptToDevtoolsControlMsg, TimelineMarker, TimelineMarkerType}; use dom::bindings::callback::ExceptionHandling; use dom::bindings::cell::DOMRefCell; @@ -35,6 +36,7 @@ use dom::globalscope::GlobalScope; use dom::history::History; use dom::htmliframeelement::build_mozbrowser_custom_event; use dom::location::Location; +use dom::mediaquerylist::{MediaQueryList, WeakMediaQueryListVec}; use dom::messageevent::MessageEvent; use dom::navigator::Navigator; use dom::node::{Node, from_untrusted_node_address, window_from_node}; @@ -86,6 +88,7 @@ use std::sync::mpsc::TryRecvError::{Disconnected, Empty}; use string_cache::Atom; use style::context::ReflowGoal; use style::error_reporting::ParseErrorReporter; +use style::media_queries; use style::properties::longhands::overflow_x; use style::selector_impl::PseudoElement; use style::str::HTML_SPACE_CHARACTERS; @@ -233,6 +236,9 @@ pub struct Window { /// A list of scroll offsets for each scrollable element. scroll_offsets: DOMRefCell<HashMap<UntrustedNodeAddress, Point2D<f32>>>, + + /// All the MediaQueryLists we need to update + media_query_lists: WeakMediaQueryListVec, } impl Window { @@ -309,6 +315,10 @@ impl Window { pub fn set_scroll_offsets(&self, offsets: HashMap<UntrustedNodeAddress, Point2D<f32>>) { *self.scroll_offsets.borrow_mut() = offsets } + + pub fn current_viewport(&self) -> Rect<Au> { + self.current_viewport.clone().get() + } } #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] @@ -856,6 +866,16 @@ impl WindowMethods for Window { } } + // https://drafts.csswg.org/cssom-view/#dom-window-matchmedia + fn MatchMedia(&self, query: DOMString) -> Root<MediaQueryList> { + let mut parser = Parser::new(&query); + let media_query_list = media_queries::parse_media_query_list(&mut parser); + let document = self.Document(); + let mql = MediaQueryList::new(&document, media_query_list); + self.media_query_lists.push(&*mql); + mql + } + #[allow(unrooted_must_root)] // https://fetch.spec.whatwg.org/#fetch-method fn Fetch(&self, input: RequestOrUSVString, init: &RequestInit) -> Rc<Promise> { @@ -1477,6 +1497,10 @@ impl Window { let custom_event = build_mozbrowser_custom_event(&self, event); custom_event.upcast::<Event>().fire(self.upcast()); } + + pub fn evaluate_media_queries_and_report_changes(&self) { + self.media_query_lists.evaluate_and_report_changes(); + } } impl Window { @@ -1563,6 +1587,7 @@ impl Window { ignore_further_async_events: Arc::new(AtomicBool::new(false)), error_reporter: error_reporter, scroll_offsets: DOMRefCell::new(HashMap::new()), + media_query_lists: WeakMediaQueryListVec::new(), }; WindowBinding::Wrap(runtime.cx(), win) |