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 | |
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 -->
22 files changed, 500 insertions, 20 deletions
diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index f8f0b8f0d4b..7d98caec983 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -68,6 +68,7 @@ serde = "0.8" smallvec = "0.1" string_cache = {version = "0.2.26", features = ["heap_size", "unstable"]} style = {path = "../style"} +style_traits = {path = "../style_traits"} time = "0.1.12" url = {version = "1.2", features = ["heap_size", "query_encoding"]} util = {path = "../util"} 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) diff --git a/components/script/lib.rs b/components/script/lib.rs index f2227fc363a..c679a5950d3 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -81,6 +81,7 @@ extern crate smallvec; #[macro_use(atom, ns)] extern crate string_cache; #[macro_use] extern crate style; +extern crate style_traits; extern crate time; #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] extern crate tinyfiledialogs; diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index ec3bc5b1b34..a028cd66ad5 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -2099,6 +2099,11 @@ impl ScriptThread { 0i32); uievent.upcast::<Event>().fire(window.upcast()); } + + // https://html.spec.whatwg.org/multipage/#event-loop-processing-model + // Step 7.7 - evaluate media queries and report changes + // Since we have resized, we need to re-evaluate MQLs + window.evaluate_media_queries_and_report_changes(); } /// Initiate a non-blocking fetch for a specified resource. Stores the InProgressLoad diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock index 240cd6170da..a851a6ae48e 100644 --- a/components/servo/Cargo.lock +++ b/components/servo/Cargo.lock @@ -1976,6 +1976,7 @@ dependencies = [ "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", "style 0.0.1", + "style_traits 0.0.1", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "tinyfiledialogs 0.1.0 (git+https://github.com/jdm/tinyfiledialogs)", "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/components/style/lib.rs b/components/style/lib.rs index 53d8102b6fa..de708d7a637 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -137,6 +137,8 @@ pub mod values; pub mod viewport; pub mod workqueue; +use cssparser::ToCss; +use std::fmt; use std::sync::Arc; /// The CSS properties supported by the style system. @@ -176,3 +178,16 @@ pub fn arc_ptr_eq<T: 'static>(a: &Arc<T>, b: &Arc<T>) -> bool { let b: &T = &**b; (a as *const T) == (b as *const T) } + +pub fn serialize_comma_separated_list<W, T>(dest: &mut W, list: &[T]) + -> fmt::Result where W: fmt::Write, T: ToCss { + if list.len() > 0 { + for item in &list[..list.len()-1] { + try!(item.to_css(dest)); + try!(write!(dest, ", ")); + } + list[list.len()-1].to_css(dest) + } else { + Ok(()) + } +} diff --git a/components/style/media_queries.rs b/components/style/media_queries.rs index 0537fc9fdac..216e1c33a42 100644 --- a/components/style/media_queries.rs +++ b/components/style/media_queries.rs @@ -7,9 +7,12 @@ //! [mq]: https://drafts.csswg.org/mediaqueries/ use app_units::Au; -use cssparser::{Delimiter, Parser, Token}; +use cssparser::{Delimiter, Parser, ToCss, Token}; use euclid::size::{Size2D, TypedSize2D}; use properties::longhands; +use serialize_comma_separated_list; +use std::fmt::{self, Write}; +use string_cache::Atom; use style_traits::ViewportPx; use values::specified; @@ -20,6 +23,14 @@ pub struct MediaQueryList { pub media_queries: Vec<MediaQuery> } +impl ToCss for MediaQueryList { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result + where W: fmt::Write + { + serialize_comma_separated_list(dest, &self.media_queries) + } +} + #[derive(PartialEq, Eq, Copy, Clone, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub enum Range<T> { @@ -104,20 +115,58 @@ impl MediaQuery { } } +impl ToCss for MediaQuery { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result + where W: fmt::Write + { + if self.qualifier == Some(Qualifier::Not) { + try!(write!(dest, "not ")); + } + + let mut type_ = String::new(); + match self.media_type { + MediaQueryType::All => try!(write!(type_, "all")), + MediaQueryType::MediaType(MediaType::Screen) => try!(write!(type_, "screen")), + MediaQueryType::MediaType(MediaType::Print) => try!(write!(type_, "print")), + MediaQueryType::MediaType(MediaType::Unknown(ref desc)) => try!(write!(type_, "{}", desc)), + }; + if self.expressions.is_empty() { + return write!(dest, "{}", type_) + } else if type_ != "all" || self.qualifier == Some(Qualifier::Not) { + try!(write!(dest, "{} and ", type_)); + } + for (i, &e) in self.expressions.iter().enumerate() { + try!(write!(dest, "(")); + let (mm, l) = match e { + Expression::Width(Range::Min(ref l)) => ("min", l), + Expression::Width(Range::Max(ref l)) => ("max", l), + }; + try!(write!(dest, "{}-width: ", mm)); + try!(l.to_css(dest)); + if i == self.expressions.len() - 1 { + try!(write!(dest, ")")); + } else { + try!(write!(dest, ") and ")); + } + } + Ok(()) + } +} + /// http://dev.w3.org/csswg/mediaqueries-3/#media0 -#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub enum MediaQueryType { All, // Always true MediaType(MediaType), } -#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub enum MediaType { Screen, Print, - Unknown, + Unknown(Atom), } #[derive(Debug)] @@ -181,7 +230,7 @@ impl MediaQuery { "screen" => MediaQueryType::MediaType(MediaType::Screen), "print" => MediaQueryType::MediaType(MediaType::Print), "all" => MediaQueryType::All, - _ => MediaQueryType::MediaType(MediaType::Unknown) + _ => MediaQueryType::MediaType(MediaType::Unknown(Atom::from(&*ident))) } } else { // Media type is only optional if qualifier is not specified. @@ -233,8 +282,8 @@ impl MediaQueryList { self.media_queries.iter().any(|mq| { // Check if media matches. Unknown media never matches. let media_match = match mq.media_type { - MediaQueryType::MediaType(MediaType::Unknown) => false, - MediaQueryType::MediaType(media_type) => media_type == device.media_type, + MediaQueryType::MediaType(MediaType::Unknown(_)) => false, + MediaQueryType::MediaType(ref media_type) => *media_type == device.media_type, MediaQueryType::All => true, }; diff --git a/ports/cef/Cargo.lock b/ports/cef/Cargo.lock index 47af71d1852..26207e9bba1 100644 --- a/ports/cef/Cargo.lock +++ b/ports/cef/Cargo.lock @@ -1827,6 +1827,7 @@ dependencies = [ "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", "style 0.0.1", + "style_traits 0.0.1", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "tinyfiledialogs 0.1.0 (git+https://github.com/jdm/tinyfiledialogs)", "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/tests/unit/style/media_queries.rs b/tests/unit/style/media_queries.rs index 4565b57ae1c..46757bb9bf0 100644 --- a/tests/unit/style/media_queries.rs +++ b/tests/unit/style/media_queries.rs @@ -6,6 +6,7 @@ use app_units::Au; use cssparser::{Parser, SourcePosition}; use euclid::size::TypedSize2D; use std::borrow::ToOwned; +use string_cache::Atom; use style::error_reporting::ParseErrorReporter; use style::media_queries::*; use style::parser::ParserContextExtraData; @@ -126,7 +127,7 @@ fn test_mq_unknown() { assert!(list.media_queries.len() == 1, css.to_owned()); let q = &list.media_queries[0]; assert!(q.qualifier == None, css.to_owned()); - assert!(q.media_type == MediaQueryType::MediaType(MediaType::Unknown), css.to_owned()); + assert!(q.media_type == MediaQueryType::MediaType(MediaType::Unknown(Atom::from("fridge"))), css.to_owned()); assert!(q.expressions.len() == 0, css.to_owned()); }); @@ -134,7 +135,7 @@ fn test_mq_unknown() { assert!(list.media_queries.len() == 1, css.to_owned()); let q = &list.media_queries[0]; assert!(q.qualifier == Some(Qualifier::Only), css.to_owned()); - assert!(q.media_type == MediaQueryType::MediaType(MediaType::Unknown), css.to_owned()); + assert!(q.media_type == MediaQueryType::MediaType(MediaType::Unknown(Atom::from("glass"))), css.to_owned()); assert!(q.expressions.len() == 0, css.to_owned()); }); @@ -142,7 +143,7 @@ fn test_mq_unknown() { assert!(list.media_queries.len() == 1, css.to_owned()); let q = &list.media_queries[0]; assert!(q.qualifier == Some(Qualifier::Not), css.to_owned()); - assert!(q.media_type == MediaQueryType::MediaType(MediaType::Unknown), css.to_owned()); + assert!(q.media_type == MediaQueryType::MediaType(MediaType::Unknown(Atom::from("wood"))), css.to_owned()); assert!(q.expressions.len() == 0, css.to_owned()); }); } @@ -247,7 +248,7 @@ fn test_mq_expressions() { assert!(list.media_queries.len() == 1, css.to_owned()); let q = &list.media_queries[0]; assert!(q.qualifier == None, css.to_owned()); - assert!(q.media_type == MediaQueryType::MediaType(MediaType::Unknown), css.to_owned()); + assert!(q.media_type == MediaQueryType::MediaType(MediaType::Unknown(Atom::from("fridge"))), css.to_owned()); assert!(q.expressions.len() == 1, css.to_owned()); match q.expressions[0] { Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(52))), diff --git a/tests/wpt/metadata-css/cssom-view-1_dev/html/window-interface.htm.ini b/tests/wpt/metadata-css/cssom-view-1_dev/html/window-interface.htm.ini index fb15f5287a8..b980676f43e 100644 --- a/tests/wpt/metadata-css/cssom-view-1_dev/html/window-interface.htm.ini +++ b/tests/wpt/metadata-css/cssom-view-1_dev/html/window-interface.htm.ini @@ -3,6 +3,3 @@ [window_inherited_functions] expected: FAIL - [window_functions] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/the-window-object/window-properties.html.ini b/tests/wpt/metadata/html/browsers/the-window-object/window-properties.html.ini index 9af9ef4196b..905abfcd492 100644 --- a/tests/wpt/metadata/html/browsers/the-window-object/window-properties.html.ini +++ b/tests/wpt/metadata/html/browsers/the-window-object/window-properties.html.ini @@ -24,9 +24,6 @@ [Window method: getSelection] expected: FAIL - [Window method: matchMedia] - expected: FAIL - [Window readonly attribute: applicationCache] expected: FAIL diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index fe55e10d9b2..9a3d311ad01 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -6566,6 +6566,12 @@ "url": "/_mozilla/css/float_relative_to_position.html" } ], + "css/matchMedia.html": [ + { + "path": "css/matchMedia.html", + "url": "/_mozilla/css/matchMedia.html" + } + ], "css/media_calc_crash.html": [ { "path": "css/media_calc_crash.html", diff --git a/tests/wpt/mozilla/meta/css/matchMedia.html.ini b/tests/wpt/mozilla/meta/css/matchMedia.html.ini new file mode 100644 index 00000000000..00c4a685473 --- /dev/null +++ b/tests/wpt/mozilla/meta/css/matchMedia.html.ini @@ -0,0 +1,15 @@ +[matchMedia.html] + type: testharness + expected: OK + + [window.matchMedia exists] + expected: FAIL + + [MediaQueryList.matches for "(min-aspect-ratio: 1/1)"] + expected: FAIL + + [MediaQueryList.matches for "(width: 200px)"] + expected: FAIL + + [Resize iframe from 200x100 to 200x50, then to 100x50] + expected: FAIL diff --git a/tests/wpt/mozilla/tests/css/blank.html b/tests/wpt/mozilla/tests/css/blank.html new file mode 100644 index 00000000000..82055fc37ee --- /dev/null +++ b/tests/wpt/mozilla/tests/css/blank.html @@ -0,0 +1 @@ +<!--intentionally blank--> diff --git a/tests/wpt/mozilla/tests/css/matchMedia.html b/tests/wpt/mozilla/tests/css/matchMedia.html new file mode 100644 index 00000000000..0eeb007a49a --- /dev/null +++ b/tests/wpt/mozilla/tests/css/matchMedia.html @@ -0,0 +1,188 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> + <head> + <title>CSS Test: CSSOM View matchMedia and MediaQueryList</title> + <link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com"> + <link rel="help" href="http://www.w3.org/TR/cssom-view/#dom-window-matchmedia"> + <link rel="help" href="http://www.w3.org/TR/cssom-view/#the-mediaquerylist-interface"> + <link rel="help" href="http://www.w3.org/TR/cssom-1/#serializing-media-queries"> + <meta name="flags" content="dom"> + <script src="/resources/testharness.js" type="text/javascript"></script> + <script src="/resources/testharnessreport.js" type="text/javascript"></script> + <style type="text/css"> + iframe { border: none; } + </style> + </head> + <body> + <noscript>Test not run - javascript required.</noscript> + <div id="log"></div> + <iframe width="200" height="100" src="blank.html"></iframe> + <script type="text/javascript"> + function reflow(doc) { + doc.body.offsetWidth; + } + + var iframe = document.querySelector("iframe"); + iframe.onload = function(){ + var iframe_window = iframe.contentWindow; + + reflow(iframe_window.document); + + test(function(){ + assert_inherits(window, "matchMedia"); + }, "window.matchMedia exists"); + + test(function(){ + assert_true(window.matchMedia instanceof Function, "FATAL ERROR: The window.matchMedia function is not present. The rest of the testsuite will fail to run."); + }, "window.matchMedia is a Function"); + + var mql, mql1, mql2, mql3; + + test(function(){ + mql = window.matchMedia("all"); + assert_true(mql instanceof MediaQueryList, "matchMedia(\"all\") returned MediaQueryList object."); + }, "window.matchMedia(\"all\")"); + + test(function(){ + assert_idl_attribute(mql, "media", "Check that MediaQueryList.media exists."); + }, "MediaQueryList.media exists"); + + test(function(){ + assert_readonly(mql, "media", "Check that MediaQueryList.media is readonly."); + }, "MediaQueryList.media is readonly"); + + test(function(){ + assert_equals(mql.media, "all"); + }, "MediaQueryList.media for \"all\""); + + test(function(){ + assert_idl_attribute(mql, "matches", "Check that MediaQueryList.matches exists."); + }, "MediaQueryList.matches exists"); + + test(function(){ + assert_readonly(mql, "matches", "Check that MediaQueryList.matches is readonly."); + }, "MediaQueryList.matches is readonly"); + + test(function(){ + assert_true(mql.matches); + }, "MediaQueryList.matches for \"all\""); + + test(function(){ + assert_inherits(mql, "addListener"); + }, "MediaQueryList.addListener exists"); + + test(function(){ + assert_true(mql.addListener instanceof Function); + }, "MediaQueryList.addListener is a Function"); + + test(function(){ + assert_inherits(mql, "removeListener"); + }, "MediaQueryList.removeListener exists"); + + test(function(){ + assert_true(mql.removeListener instanceof Function); + }, "MediaQueryList.removeListener is a Function"); + + test(function(){ + mql = window.matchMedia("::"); + assert_true(mql instanceof MediaQueryList, "window.matchMedia(\"::\") returned MediaQueryList object."); + assert_equals(mql.media, "not all", "MediaQueryList.media serialized as \"not all\" from original string with syntax error."); + }, "MediaQueryList.media syntax error"); + + test(function(){ + assert_false(mql.matches); + }, "MediaQueryList.matches for \"not all\""); + + test(function(){ + mql = iframe_window.matchMedia("(max-width: 199px), all and (min-width: 200px)"); + assert_equals(mql.media, "(max-width: 199px), (min-width: 200px)"); + assert_true(mql.matches); + }, "MediaQueryList.matches for \"(max-width: 199px), all and (min-width: 200px)\"") + + test(function(){ + mql = iframe_window.matchMedia("(min-aspect-ratio: 1/1)"); + assert_true(mql.matches); + }, "MediaQueryList.matches for \"(min-aspect-ratio: 1/1)\""); + + test(function(){ + mql = iframe_window.matchMedia("(width: 200px)"); + assert_true(mql.matches); + }, "MediaQueryList.matches for \"(width: 200px)\""); + + test(function(){ + mql1 = iframe_window.matchMedia("(max-height: 50px)"); + assert_false(mql1.matches); + }, "MediaQueryList.matches for \"(max-height: 50px)\""); + + test(function(){ + mql2 = iframe_window.matchMedia("(min-width: 150px)"); + assert_true(mql2.matches); + }, "MediaQueryList.matches for \"(min-width: 150px)\""); + + var resizeTest = async_test("Resize iframe from 200x100 to 200x50, then to 100x50"); + var listenerOrderTest = async_test("Listeners are called in the order which they have been added"); + var duplicateListenerTest = async_test("Listener added twice is only called once."); + + window.onload = function(){ + + var rmListener = function(x){ + resizeTest.step(function(){ + assert_unreached("removeListener was not successful."); + }); + }; + + var dupListener = function(x){ + duplicateListenerTest.step(function(){ + assert_false(mql1.dupListenerCalled, "Check that this listener has not been called before."); + mql1.dupListenerCalled = true; + }); + }; + + mql1.firstListenerCalled = false; + mql1.dupListenerCalled = false; + // Add listener twice and remove it below. Should not be called. + mql1.addListener(rmListener); + mql1.addListener(rmListener); + // Add listener twice. Should only be called once. + mql1.addListener(dupListener); + mql1.addListener(dupListener); + + mql1.addListener(function(x){ + resizeTest.step(function(){ + assert_equals(x, mql1, "Check that the MediaQueryList passed to the handler is the same that addListener was invoked on."); + assert_true(x.matches, "(max-height: 50px) should now pass."); + assert_true(mql2.matches, "(min-width: 150px) should still pass."); + iframe.width = "100"; + }); + + listenerOrderTest.step(function(){ + assert_false(mql1.firstListenerCalled, "Check that this listener is only called once."); + mql1.firstListenerCalled = true; + }); + }); + + mql1.addListener(function(x){ + listenerOrderTest.step(function(){ + assert_true(mql1.firstListenerCalled, "Check that the listener added last is called last."); + }); + listenerOrderTest.done(); + }); + + mql1.removeListener(rmListener); + + mql2.addListener(function(x){ + duplicateListenerTest.done(); + resizeTest.step(function(){ + assert_equals(x, mql2, "Check that the MediaQueryList passed to the handler is the same that addListener was invoked on."); + assert_true(mql1.matches, "(max-height: 50px) should still pass."); + assert_false(x.matches, "(min-width: 150px) should now fail."); + }); + resizeTest.done(); + }); + + iframe.height = "50"; + }; + }; + </script> + </body> +</html> diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.html b/tests/wpt/mozilla/tests/mozilla/interfaces.html index 8c318e4d7d3..65dc554e274 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.html +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.html @@ -128,6 +128,7 @@ test_interfaces([ "KeyboardEvent", "Location", "MediaError", + "MediaQueryList", "MessageEvent", "MimeType", "MimeTypeArray", |