1use cssparser::{Parser, ParserInput, serialize_identifier};
4use selectors::matching::QuirksMode;
5use style::media_queries::MediaList;
6use style::parser::ParserContext;
7use style::properties::{
8 ComputedValues, Importance, IndexedId, LonghandId, PropertyDeclaration,
9 PropertyDeclarationBlock, PropertyDeclarationId, PropertyId, SourcePropertyDeclaration,
10 parse_style_attribute,
11};
12use style::selector_parser::PseudoElement;
13use style::servo_arc::Arc as ServoArc;
14use style::shared_lock::ToCssWithGuard;
15use style::stylesheets::supports_rule::{Declaration, SupportsCondition};
16use style::stylesheets::{
17 CssRuleType, CustomMediaEvaluator, DocumentStyleSheet, Origin, OriginSet, StylesheetInDocument,
18};
19use style_traits::{CssString, ParsingMode, ToCss};
20
21use crate::{BaseDocument, NodeId, local_name};
22
23pub struct PlatformMediaQuery {
25 list: MediaList,
26}
27
28impl PlatformMediaQuery {
29 pub fn media(&self) -> String {
31 self.list.to_css_string()
32 }
33}
34
35pub struct PlatformStyleSheet {
37 sheet: DocumentStyleSheet,
38 href: Option<String>,
39 origin_clean: bool,
40}
41
42impl PlatformStyleSheet {
43 pub fn href(&self) -> Option<&str> {
45 self.href.as_deref()
46 }
47
48 pub fn disabled(&self) -> bool {
50 self.sheet.0.disabled()
51 }
52
53 pub fn media(&self) -> String {
55 let guard = self.sheet.0.shared_lock.read();
56 self.sheet.0.media.read_with(&guard).to_css_string()
57 }
58
59 pub fn media_items(&self) -> Vec<String> {
61 let guard = self.sheet.0.shared_lock.read();
62 self.sheet
63 .0
64 .media
65 .read_with(&guard)
66 .media_queries
67 .iter()
68 .map(ToCss::to_css_string)
69 .collect()
70 }
71
72 pub fn rules(&self) -> Option<Vec<String>> {
74 if !self.origin_clean {
75 return None;
76 }
77 let guard = self.sheet.0.shared_lock.read();
78 Some(
79 self.sheet
80 .contents(&guard)
81 .rules(&guard)
82 .iter()
83 .map(|rule| {
84 let mut text = CssString::new();
85 rule.to_css(&guard, &mut text)
86 .expect("writing CSS to a string cannot fail");
87 text.to_string()
88 })
89 .collect(),
90 )
91 }
92}
93
94impl BaseDocument {
95 fn platform_parser_context<'a>(
96 &'a self,
97 url_data: &'a style::stylesheets::UrlExtraData,
98 ) -> ParserContext<'a> {
99 ParserContext::new(
100 Origin::Author,
101 url_data,
102 Some(CssRuleType::Style),
103 ParsingMode::DEFAULT,
104 QuirksMode::NoQuirks,
105 Default::default(),
106 None,
107 None,
108 Default::default(),
109 )
110 }
111
112 pub fn platform_supports(&self, condition: &str) -> bool {
114 let url_data = self.url.url_extra_data();
115 let mut context = self.platform_parser_context(&url_data);
116 let mut input = ParserInput::new(condition);
117 if let Ok(condition) = Parser::new(&mut input).parse_entirely(SupportsCondition::parse) {
118 return condition.eval(&mut context);
119 }
120 let mut input = ParserInput::new(condition);
122 Parser::new(&mut input)
123 .parse_entirely(Declaration::parse)
124 .is_ok_and(|declaration| declaration.eval(&mut context))
125 }
126
127 pub fn platform_supports_property(&self, name: &str, value: &str) -> bool {
129 let mut declaration = String::new();
130 serialize_identifier(name, &mut declaration)
131 .expect("writing an identifier to a string cannot fail");
132 declaration.push(':');
133 declaration.push_str(value);
134 self.platform_supports(&declaration)
135 }
136
137 pub fn platform_media_query(&self, text: &str) -> PlatformMediaQuery {
139 let url_data = self.url.url_extra_data();
140 let mut context = self.platform_parser_context(&url_data);
141 let mut input = ParserInput::new(text);
142 PlatformMediaQuery {
143 list: MediaList::parse(&mut context, &mut Parser::new(&mut input)),
144 }
145 }
146
147 pub fn platform_media_matches(&self, query: &PlatformMediaQuery) -> bool {
149 query.list.evaluate(
150 self.stylist.device(),
151 QuirksMode::NoQuirks,
152 &mut CustomMediaEvaluator::none(),
153 )
154 }
155
156 fn platform_computed_values(
157 &self,
158 node_id: NodeId,
159 pseudo: &str,
160 ) -> Option<ServoArc<ComputedValues>> {
161 let node = self.get_node(node_id)?;
162 if !node.is_element() || !node.flags.is_in_document() {
163 return None;
164 }
165 if pseudo.is_empty() {
166 return node.primary_styles().map(|style| ServoArc::clone(&style));
167 }
168 let pseudo = match pseudo {
169 "::before" => PseudoElement::Before,
170 "::after" => PseudoElement::After,
171 _ => return None,
172 };
173 let data = node.stylo_element_data_opt()?.get()?;
174 data.styles.pseudos.get(&pseudo).cloned()
175 }
176
177 pub fn platform_computed_names(&self, node_id: NodeId, pseudo: &str) -> Vec<String> {
179 let Some(style) = self.platform_computed_values(node_id, pseudo) else {
180 return Vec::new();
181 };
182 let mut names = Vec::new();
183 for index in 0..LonghandId::COUNT {
184 let id = unsafe { LonghandId::from_index_release_unchecked(index) };
186 if PropertyId::parse_enabled_for_all_content(id.name()).is_ok() {
187 names.push(id.name().to_string());
188 }
189 }
190 names.sort_unstable();
191 let properties = style.custom_properties();
192 let count = properties.inherited.len() + properties.non_inherited.len();
193 for index in 0..count {
194 if let Some((name, Some(_))) = properties.property_at(index) {
195 names.push(format!("--{name}"));
196 }
197 }
198 names
199 }
200
201 pub fn platform_computed_value(&self, node_id: NodeId, pseudo: &str, name: &str) -> String {
203 let Some(style) = self.platform_computed_values(node_id, pseudo) else {
204 return String::new();
205 };
206 let Ok(property) = PropertyId::parse_enabled_for_all_content(name) else {
207 return String::new();
208 };
209 let declaration = match property.as_shorthand() {
210 Ok(_) => return String::new(),
211 Err(declaration) => declaration,
212 };
213
214 let geometry_id = self.get_node(node_id).and_then(|node| match pseudo {
216 "" => Some(node_id),
217 "::before" => node.before(),
218 "::after" => node.after(),
219 _ => None,
220 });
221 if matches!(
222 name,
223 "width"
224 | "height"
225 | "padding-top"
226 | "padding-right"
227 | "padding-bottom"
228 | "padding-left"
229 | "margin-top"
230 | "margin-right"
231 | "margin-bottom"
232 | "margin-left"
233 | "border-top-width"
234 | "border-right-width"
235 | "border-bottom-width"
236 | "border-left-width"
237 ) {
238 if let Some(properties) = geometry_id
239 .and_then(|id| self.get_node(id))
240 .and_then(|node| node.computed_style_properties())
241 {
242 if let Some((_, value)) = properties.into_iter().find(|(key, _)| *key == name) {
243 return value;
244 }
245 }
246 }
247 style.computed_value_to_string(declaration)
248 }
249
250 fn platform_inline_block(&self, node_id: NodeId) -> PropertyDeclarationBlock {
251 let css = self
252 .get_node(node_id)
253 .and_then(|node| node.attr(local_name!("style")))
254 .unwrap_or_default();
255 parse_style_attribute(
256 css,
257 &self.url.url_extra_data(),
258 None,
259 QuirksMode::NoQuirks,
260 CssRuleType::Style,
261 )
262 }
263
264 pub fn platform_inline_text(&self, node_id: NodeId) -> String {
266 let mut text = CssString::new();
267 self.platform_inline_block(node_id)
268 .to_css(&mut text)
269 .expect("writing CSS to a string cannot fail");
270 text.to_string()
271 }
272
273 pub fn platform_inline_names(&self, node_id: NodeId) -> Vec<String> {
275 self.platform_inline_block(node_id)
276 .declarations()
277 .iter()
278 .map(|declaration| match declaration.id() {
279 PropertyDeclarationId::Longhand(id) => id.name().to_string(),
280 PropertyDeclarationId::Custom(name) => format!("--{name}"),
281 })
282 .collect()
283 }
284
285 pub fn platform_inline_value(&self, node_id: NodeId, name: &str, priority: bool) -> String {
287 let url_data = self.url.url_extra_data();
288 let context = self.platform_parser_context(&url_data);
289 let Ok(property) = PropertyId::parse(name, &context) else {
290 return String::new();
291 };
292 let block = self.platform_inline_block(node_id);
293 if priority {
294 return if block.property_priority(&property).important() {
295 "important".to_string()
296 } else {
297 String::new()
298 };
299 }
300 let mut text = CssString::new();
301 block
302 .property_value_to_css(&property, &mut text)
303 .expect("writing CSS to a string cannot fail");
304 text.to_string()
305 }
306
307 pub fn platform_edit_inline(
311 &self,
312 node_id: NodeId,
313 name: &str,
314 value: &str,
315 priority: &str,
316 ) -> Option<String> {
317 let url_data = self.url.url_extra_data();
318 let context = self.platform_parser_context(&url_data);
319 let property = PropertyId::parse(name, &context).ok()?;
320 let mut source = SourcePropertyDeclaration::default();
321 let importance = if priority.is_empty() {
322 Importance::Normal
323 } else if priority.eq_ignore_ascii_case("important") {
324 Importance::Important
325 } else if !value.is_empty() {
326 return None;
327 } else {
328 Importance::Normal
329 };
330 if !value.is_empty() {
331 let mut input = ParserInput::new(value);
332 Parser::new(&mut input)
333 .parse_entirely(|input| {
334 PropertyDeclaration::parse_into(&mut source, property.clone(), &context, input)
335 })
336 .ok()?;
337 }
338 let mut block = self.platform_inline_block(node_id);
339 if let Some(index) = block.first_declaration_to_remove(&property) {
340 block.remove_property(&property, index);
341 }
342 if !value.is_empty() {
343 block.extend(source.drain(), importance);
344 }
345 let mut text = CssString::new();
346 block
347 .to_css(&mut text)
348 .expect("writing CSS to a string cannot fail");
349 Some(text.to_string())
350 }
351
352 pub fn platform_sheet_owners(&self, root_id: NodeId) -> Vec<NodeId> {
354 let mut owners = Vec::new();
355 let mut stack = vec![root_id];
356 while let Some(id) = stack.pop() {
357 let Some(node) = self.get_node(id) else {
358 continue;
359 };
360 if self.nodes_to_stylesheet.contains_key(&id) {
361 owners.push(id);
362 }
363 stack.extend(node.children.iter().rev().copied());
364 }
365 owners
366 }
367
368 pub fn platform_sheet(&self, owner: NodeId) -> Option<PlatformStyleSheet> {
370 let sheet = self.nodes_to_stylesheet.get(&owner)?.clone();
371 let node = self.get_node(owner)?;
372 let guard = sheet.0.shared_lock.read();
373 let source_url = &sheet.contents(&guard).url_data.0;
374 let linked = node
375 .element_data()
376 .is_some_and(|element| element.name.local == local_name!("link"));
377 let href = linked.then(|| source_url.to_string());
378 let origin_clean = !linked || source_url.origin() == self.url.origin();
379 drop(guard);
380 Some(PlatformStyleSheet {
381 sheet,
382 href,
383 origin_clean,
384 })
385 }
386
387 pub(crate) fn platform_initial_sheet_media(&self, sheet: &DocumentStyleSheet, owner: NodeId) {
389 let text = self
390 .get_node(owner)
391 .and_then(|node| node.attr(local_name!("media")))
392 .unwrap_or_default();
393 let query = self.platform_media_query(text);
394 *sheet.0.media.write_with(&mut sheet.0.shared_lock.write()) = query.list;
395 }
396
397 pub fn platform_disable_sheet(&mut self, sheet: &PlatformStyleSheet, disabled: bool) {
399 if sheet.sheet.0.set_disabled(disabled) {
400 self.stylist
401 .force_stylesheet_origins_dirty(OriginSet::all());
402 }
403 }
404
405 pub fn platform_set_sheet_media(&mut self, sheet: &PlatformStyleSheet, text: &str) {
407 let query = self.platform_media_query(text);
408 *sheet
409 .sheet
410 .0
411 .media
412 .write_with(&mut sheet.sheet.0.shared_lock.write()) = query.list;
413 self.stylist
414 .force_stylesheet_origins_dirty(OriginSet::all());
415 }
416
417 pub fn platform_edit_sheet_medium(
419 &mut self,
420 sheet: &PlatformStyleSheet,
421 medium: &str,
422 delete: bool,
423 ) -> bool {
424 let url_data = self.url.url_extra_data();
425 let context = self.platform_parser_context(&url_data);
426 let changed = {
427 let mut guard = sheet.sheet.0.shared_lock.write();
428 let list = sheet.sheet.0.media.write_with(&mut guard);
429 if delete {
430 list.delete_medium(&context, medium)
431 } else {
432 list.append_medium(&context, medium)
433 }
434 };
435 if changed {
436 self.stylist
437 .force_stylesheet_origins_dirty(OriginSet::all());
438 }
439 changed
440 }
441
442 pub fn platform_elements_from_point(&self, x: f32, y: f32) -> Vec<NodeId> {
444 let viewport = self.get_viewport();
445 let width = viewport.window_size.0 as f64 / viewport.scale_f64();
446 let height = viewport.window_size.1 as f64 / viewport.scale_f64();
447 if !x.is_finite()
448 || !y.is_finite()
449 || x < 0.0
450 || y < 0.0
451 || f64::from(x) >= width
452 || f64::from(y) >= height
453 {
454 return Vec::new();
455 }
456 let Some(root) = self.try_root_element() else {
457 return Vec::new();
458 };
459 let mut hits = Some(Vec::new());
460 root.hit_inner_collect(x, y, viewport.scale_f64(), &mut None, &mut hits);
461 let mut seen = std::collections::HashSet::new();
462 hits.unwrap_or_default()
463 .into_iter()
464 .filter_map(|hit| {
465 let mut id = self.nearest_non_anonymous_ancestor(hit.node_id)?;
466 loop {
467 let node = self.get_node(id)?;
468 if node.is_element() {
469 return seen.insert(id).then_some(id);
470 }
471 id = node.parent?;
472 }
473 })
474 .collect()
475 }
476}