Skip to main content

script/dom/media/
medialist.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::RefCell;
6
7use cssparser::{Parser, ParserInput};
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
11use servo_arc::Arc;
12use style::media_queries::{MediaList as StyleMediaList, MediaQuery};
13use style::parser::ParserContext;
14use style::shared_lock::{Locked, SharedRwLock};
15use style::stylesheets::{CssRuleType, CustomMediaEvaluator, Origin, UrlExtraData};
16use style_traits::{ParseError, ParsingMode, ToCss};
17
18use crate::css::parser_context_for_document;
19use crate::dom::bindings::codegen::Bindings::MediaListBinding::MediaListMethods;
20use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
21use crate::dom::bindings::reflector::DomGlobal;
22use crate::dom::bindings::root::{Dom, DomRoot};
23use crate::dom::bindings::str::DOMString;
24use crate::dom::css::cssstylesheet::CSSStyleSheet;
25use crate::dom::document::Document;
26use crate::dom::node::NodeTraits;
27use crate::dom::window::Window;
28
29#[dom_struct]
30pub(crate) struct MediaList {
31    reflector_: Reflector,
32    parent_stylesheet: Dom<CSSStyleSheet>,
33    #[ignore_malloc_size_of = "Stylo"]
34    #[no_trace]
35    media_queries: RefCell<Arc<Locked<StyleMediaList>>>,
36}
37
38impl MediaList {
39    pub(crate) fn new_inherited(
40        parent_stylesheet: &CSSStyleSheet,
41        media_queries: Arc<Locked<StyleMediaList>>,
42    ) -> MediaList {
43        MediaList {
44            parent_stylesheet: Dom::from_ref(parent_stylesheet),
45            reflector_: Reflector::new(),
46            media_queries: RefCell::new(media_queries),
47        }
48    }
49
50    pub(crate) fn new(
51        cx: &mut JSContext,
52        window: &Window,
53        parent_stylesheet: &CSSStyleSheet,
54        media_queries: Arc<Locked<StyleMediaList>>,
55    ) -> DomRoot<MediaList> {
56        reflect_dom_object_with_cx(
57            Box::new(MediaList::new_inherited(parent_stylesheet, media_queries)),
58            window,
59            cx,
60        )
61    }
62
63    fn shared_lock(&self) -> &SharedRwLock {
64        self.parent_stylesheet.shared_lock()
65    }
66
67    /// <https://drafts.csswg.org/cssom/#parse-a-media-query-list>
68    pub(crate) fn parse_media_list(value: &str, window: &Window) -> StyleMediaList {
69        if value.is_empty() {
70            return StyleMediaList::empty();
71        }
72        let mut input = ParserInput::new(value);
73        let mut parser = Parser::new(&mut input);
74        let document = window.Document();
75        let url_data = UrlExtraData(document.owner_global().api_base_url().get_arc());
76        // FIXME(emilio): This looks somewhat fishy, since we use the context
77        // only to parse the media query list, CssRuleType::Media doesn't make
78        // much sense.
79        let context = parser_context_for_document(
80            &document,
81            CssRuleType::Media,
82            ParsingMode::DEFAULT,
83            &url_data,
84        );
85        StyleMediaList::parse(&context, &mut parser)
86    }
87
88    /// <https://drafts.csswg.org/cssom/#parse-a-media-query>
89    pub(crate) fn parse_media_query<'i>(
90        value: &'i str,
91        window: &Window,
92    ) -> Result<MediaQuery, ParseError<'i>> {
93        let mut input = ParserInput::new(value);
94        let mut parser = Parser::new(&mut input);
95        let document = window.Document();
96        let url_data = UrlExtraData(document.owner_global().api_base_url().get_arc());
97        let context = parser_context_for_document(
98            &document,
99            CssRuleType::Media,
100            ParsingMode::DEFAULT,
101            &url_data,
102        );
103        MediaQuery::parse(&context, &mut parser)
104    }
105
106    pub(crate) fn clone_media_list(&self) -> StyleMediaList {
107        let guard = self.shared_lock().read();
108        self.media_queries.borrow().read_with(&guard).clone()
109    }
110
111    pub(crate) fn update_media_list(&self, media_queries: Arc<Locked<StyleMediaList>>) {
112        *self.media_queries.borrow_mut() = media_queries;
113    }
114
115    /// <https://html.spec.whatwg.org/multipage/#matches-the-environment>
116    pub(crate) fn matches_environment(document: &Document, media_query: &str) -> bool {
117        let quirks_mode = document.quirks_mode();
118        let url_data = UrlExtraData(document.owner_global().api_base_url().get_arc());
119        // FIXME(emilio): This should do the same that we do for other media
120        // lists regarding the rule type and such, though it doesn't really
121        // matter right now...
122        //
123        // Also, ParsingMode::all() is wrong, and should be DEFAULT.
124        let context = ParserContext::new(
125            Origin::Author,
126            &url_data,
127            Some(CssRuleType::Style),
128            ParsingMode::all(),
129            quirks_mode,
130            /* namespaces = */ Default::default(),
131            None,
132            None,
133            /* attr_taint = */ Default::default(),
134        );
135        let mut parser_input = ParserInput::new(media_query);
136        let mut parser = Parser::new(&mut parser_input);
137        let media_list = StyleMediaList::parse(&context, &mut parser);
138        media_list.evaluate(
139            document.window().layout().device(),
140            quirks_mode,
141            &mut CustomMediaEvaluator::none(),
142        )
143    }
144}
145
146impl MediaListMethods<crate::DomTypeHolder> for MediaList {
147    /// <https://drafts.csswg.org/cssom/#dom-medialist-mediatext>
148    fn MediaText(&self) -> DOMString {
149        let guard = self.shared_lock().read();
150        DOMString::from(
151            self.media_queries
152                .borrow()
153                .read_with(&guard)
154                .to_css_string(),
155        )
156    }
157
158    /// <https://drafts.csswg.org/cssom/#dom-medialist-mediatext>
159    fn SetMediaText(&self, value: DOMString) {
160        self.parent_stylesheet.will_modify();
161        let global = self.global();
162        let mut guard = self.shared_lock().write();
163        let media_queries_borrowed = self.media_queries.borrow();
164        let media_queries = media_queries_borrowed.write_with(&mut guard);
165        *media_queries = Self::parse_media_list(&value.str(), global.as_window());
166        self.parent_stylesheet.notify_invalidations();
167    }
168
169    /// <https://drafts.csswg.org/cssom/#dom-medialist-length>
170    fn Length(&self) -> u32 {
171        let guard = self.shared_lock().read();
172        self.media_queries
173            .borrow()
174            .read_with(&guard)
175            .media_queries
176            .len() as u32
177    }
178
179    /// <https://drafts.csswg.org/cssom/#dom-medialist-item>
180    fn Item(&self, index: u32) -> Option<DOMString> {
181        let guard = self.shared_lock().read();
182        self.media_queries
183            .borrow()
184            .read_with(&guard)
185            .media_queries
186            .get(index as usize)
187            .map(|query| query.to_css_string().into())
188    }
189
190    /// <https://drafts.csswg.org/cssom/#dom-medialist-item>
191    fn IndexedGetter(&self, index: u32) -> Option<DOMString> {
192        self.Item(index)
193    }
194
195    /// <https://drafts.csswg.org/cssom/#dom-medialist-appendmedium>
196    fn AppendMedium(&self, medium: DOMString) {
197        // Step 1
198        let global = self.global();
199        let medium = medium.str();
200        let m = Self::parse_media_query(&medium, global.as_window());
201        // Step 2
202        if m.is_err() {
203            return;
204        }
205        // Step 3
206        {
207            let m_serialized = m.clone().unwrap().to_css_string();
208            let guard = self.shared_lock().read();
209            let any = self
210                .media_queries
211                .borrow()
212                .read_with(&guard)
213                .media_queries
214                .iter()
215                .any(|q| m_serialized == q.to_css_string());
216            if any {
217                return;
218            }
219        }
220        // Step 4
221        self.parent_stylesheet.will_modify();
222        let mut guard = self.shared_lock().write();
223        self.media_queries
224            .borrow()
225            .write_with(&mut guard)
226            .media_queries
227            .push(m.unwrap());
228        self.parent_stylesheet.notify_invalidations();
229    }
230
231    /// <https://drafts.csswg.org/cssom/#dom-medialist-deletemedium>
232    fn DeleteMedium(&self, medium: DOMString) {
233        // Step 1
234        let global = self.global();
235        let medium = medium.str();
236        let m = Self::parse_media_query(&medium, global.as_window());
237        // Step 2
238        if m.is_err() {
239            return;
240        }
241        // Step 3
242        self.parent_stylesheet.will_modify();
243        let m_serialized = m.unwrap().to_css_string();
244        let mut guard = self.shared_lock().write();
245        let media_queries_borrowed = self.media_queries.borrow();
246        let media_list = media_queries_borrowed.write_with(&mut guard);
247        let new_vec = media_list
248            .media_queries
249            .drain(..)
250            .filter(|q| m_serialized != q.to_css_string())
251            .collect();
252        media_list.media_queries = new_vec;
253        self.parent_stylesheet.notify_invalidations();
254    }
255}