servo: Merge #13453 - Implement matchMedia and MediaQueryList (from metajack:media-query-list); r=jdm

<!-- 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.

Source-Repo: https://github.com/servo/servo
Source-Revision: 6ef46ab9e4ec08d5f5226d41f0cac77c3584bae9
This commit is contained in:
Jack Moffitt 2016-11-02 14:51:12 -05:00
Родитель f59917f404
Коммит 2f97a7cbc3
15 изменённых файлов: 289 добавлений и 14 удалений

Просмотреть файл

@ -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"}

Просмотреть файл

@ -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

Просмотреть файл

@ -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]

Просмотреть файл

@ -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()
}
}

Просмотреть файл

@ -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;

Просмотреть файл

@ -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;
};

Просмотреть файл

@ -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

Просмотреть файл

@ -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)

Просмотреть файл

@ -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;

Просмотреть файл

@ -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

1
servo/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)",

Просмотреть файл

@ -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(())
}
}

Просмотреть файл

@ -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,
};

1
servo/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)",

Просмотреть файл

@ -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))),