blitz-pdf 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +26 -0
  2. package/THIRD_PARTY_NOTICES.md +5 -2
  3. package/docs/layout-regressions.md +65 -0
  4. package/docs/pagination-regressions.md +89 -0
  5. package/package.json +2 -1
  6. package/vendor/README.md +35 -0
  7. package/vendor/blitz-dom/Cargo.toml +113 -0
  8. package/vendor/blitz-dom/LICENSE-APACHE +176 -0
  9. package/vendor/blitz-dom/LICENSE-MIT +23 -0
  10. package/vendor/blitz-dom/assets/default.css +1142 -0
  11. package/vendor/blitz-dom/assets/moz-bullet-font.otf +0 -0
  12. package/vendor/blitz-dom/src/accessibility.rs +255 -0
  13. package/vendor/blitz-dom/src/config.rs +97 -0
  14. package/vendor/blitz-dom/src/cssom.rs +971 -0
  15. package/vendor/blitz-dom/src/debug.rs +206 -0
  16. package/vendor/blitz-dom/src/document.rs +3395 -0
  17. package/vendor/blitz-dom/src/events/driver.rs +427 -0
  18. package/vendor/blitz-dom/src/events/focus.rs +40 -0
  19. package/vendor/blitz-dom/src/events/ime.rs +31 -0
  20. package/vendor/blitz-dom/src/events/keyboard.rs +174 -0
  21. package/vendor/blitz-dom/src/events/mod.rs +315 -0
  22. package/vendor/blitz-dom/src/events/pointer.rs +849 -0
  23. package/vendor/blitz-dom/src/font_metrics.rs +177 -0
  24. package/vendor/blitz-dom/src/form.rs +463 -0
  25. package/vendor/blitz-dom/src/html.rs +38 -0
  26. package/vendor/blitz-dom/src/iframe.rs +186 -0
  27. package/vendor/blitz-dom/src/layout/construct.rs +1328 -0
  28. package/vendor/blitz-dom/src/layout/damage.rs +642 -0
  29. package/vendor/blitz-dom/src/layout/inline.rs +1160 -0
  30. package/vendor/blitz-dom/src/layout/list.rs +237 -0
  31. package/vendor/blitz-dom/src/layout/mod.rs +969 -0
  32. package/vendor/blitz-dom/src/layout/paint_tree.rs +462 -0
  33. package/vendor/blitz-dom/src/layout/replaced.rs +439 -0
  34. package/vendor/blitz-dom/src/layout/table.rs +967 -0
  35. package/vendor/blitz-dom/src/layout/text_transform.rs +979 -0
  36. package/vendor/blitz-dom/src/layout/writing_mode.rs +437 -0
  37. package/vendor/blitz-dom/src/lib.rs +160 -0
  38. package/vendor/blitz-dom/src/mutator.rs +1818 -0
  39. package/vendor/blitz-dom/src/net.rs +649 -0
  40. package/vendor/blitz-dom/src/node/attributes.rs +60 -0
  41. package/vendor/blitz-dom/src/node/custom_widget.rs +178 -0
  42. package/vendor/blitz-dom/src/node/element.rs +1106 -0
  43. package/vendor/blitz-dom/src/node/mod.rs +30 -0
  44. package/vendor/blitz-dom/src/node/node.rs +2036 -0
  45. package/vendor/blitz-dom/src/node/scrollbar.rs +237 -0
  46. package/vendor/blitz-dom/src/node/serialize.rs +225 -0
  47. package/vendor/blitz-dom/src/node/stylo_data.rs +142 -0
  48. package/vendor/blitz-dom/src/node/svg.rs +265 -0
  49. package/vendor/blitz-dom/src/node/text.rs +804 -0
  50. package/vendor/blitz-dom/src/query_selector.rs +275 -0
  51. package/vendor/blitz-dom/src/resolve.rs +474 -0
  52. package/vendor/blitz-dom/src/resolved_style.rs +797 -0
  53. package/vendor/blitz-dom/src/scrolling.rs +780 -0
  54. package/vendor/blitz-dom/src/selection.rs +122 -0
  55. package/vendor/blitz-dom/src/stylo.rs +1518 -0
  56. package/vendor/blitz-dom/src/stylo_device.rs +88 -0
  57. package/vendor/blitz-dom/src/stylo_to_cursor_icon.rs +49 -0
  58. package/vendor/blitz-dom/src/stylo_to_kurbo.rs +129 -0
  59. package/vendor/blitz-dom/src/stylo_to_parley.rs +655 -0
  60. package/vendor/blitz-dom/src/traversal.rs +600 -0
  61. package/vendor/blitz-dom/src/tree.rs +136 -0
  62. package/vendor/blitz-dom/src/url.rs +63 -0
  63. package/vendor/blitz-dom/src/util.rs +256 -0
  64. package/wasm/blitz_pdf_bg.wasm +0 -0
@@ -0,0 +1,971 @@
1
+ //! CSSOM stylesheet access (`document.styleSheets`, `CSSStyleSheet`,
2
+ //! `CSSRuleList`, `CSSRule` and friends) backed by stylo's stylesheet objects.
3
+ //!
4
+ //! Stylesheets are identified by the `NodeId` of their owner node (`<style>` or
5
+ //! `<link>`), and rules by a *path* of indices from the stylesheet's top-level
6
+ //! rule list down through nested rule lists (`@media`, `@supports`, nested
7
+ //! style rules, `@keyframes`, ...). An empty path denotes the stylesheet's own
8
+ //! top-level rule list.
9
+
10
+ use cssparser::Parser;
11
+ use selectors::matching::QuirksMode;
12
+ use selectors::parser::{ParseRelative, SelectorList};
13
+ use style::font_face::FontFaceRule;
14
+ use style::invalidation::stylesheets::RuleChangeKind;
15
+ use style::parser::ParserContext;
16
+ use style::properties::font_face::DescriptorId as FontFaceDescriptorId;
17
+ use style::properties::{
18
+ Importance, PropertyDeclarationBlock, PropertyId, SourcePropertyDeclaration,
19
+ parse_one_declaration_into,
20
+ };
21
+ use style::selector_parser::SelectorParser;
22
+ use style::servo_arc::Arc as ServoArc;
23
+ use style::shared_lock::{Locked, SharedRwLockReadGuard, ToCssWithGuard};
24
+ use style::stylesheets::keyframes_rule::{Keyframe, KeyframesRule};
25
+ use style::stylesheets::{
26
+ AllowImportRules, CssRule, CssRuleRef, CssRuleType, CssRuleTypes, CssRules, DocumentStyleSheet,
27
+ Origin, OriginSet, RulesMutateError, StylesheetInDocument,
28
+ };
29
+ use style_traits::{CssStringWriter, ParsingMode, ToCss};
30
+ // For `SelectorList::to_css_string`
31
+ use cssparser::ToCss as _;
32
+
33
+ use blitz_traits::node_id::NodeId;
34
+
35
+ use crate::BaseDocument;
36
+ use crate::net::StylesheetLoader;
37
+
38
+ /// Errors from CSSOM mutation operations, mirroring the `DOMException` names
39
+ /// which the corresponding JavaScript APIs throw.
40
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
41
+ pub enum CssomError {
42
+ /// The owner node has no associated stylesheet, or the rule path does not
43
+ /// resolve to a rule (list).
44
+ NotFound,
45
+ /// `SyntaxError`
46
+ Syntax,
47
+ /// `IndexSizeError`
48
+ IndexSize,
49
+ /// `HierarchyRequestError`
50
+ HierarchyRequest,
51
+ /// `InvalidStateError`
52
+ InvalidState,
53
+ /// `NotSupportedError`
54
+ NotSupported,
55
+ }
56
+
57
+ impl CssomError {
58
+ /// The `DOMException` name corresponding to this error
59
+ pub fn dom_exception_name(self) -> &'static str {
60
+ match self {
61
+ Self::NotFound => "NotFoundError",
62
+ Self::Syntax => "SyntaxError",
63
+ Self::IndexSize => "IndexSizeError",
64
+ Self::HierarchyRequest => "HierarchyRequestError",
65
+ Self::InvalidState => "InvalidStateError",
66
+ Self::NotSupported => "NotSupportedError",
67
+ }
68
+ }
69
+ }
70
+
71
+ impl From<RulesMutateError> for CssomError {
72
+ fn from(err: RulesMutateError) -> Self {
73
+ match err {
74
+ RulesMutateError::Syntax => Self::Syntax,
75
+ RulesMutateError::IndexSize => Self::IndexSize,
76
+ RulesMutateError::HierarchyRequest => Self::HierarchyRequest,
77
+ RulesMutateError::InvalidState => Self::InvalidState,
78
+ }
79
+ }
80
+ }
81
+
82
+ /// A snapshot of the CSSOM-visible attributes of a rule
83
+ #[derive(Debug, Clone)]
84
+ pub struct CssRuleInfo {
85
+ /// The name of the CSSOM interface for the rule (e.g. `"CSSStyleRule"`)
86
+ pub interface: &'static str,
87
+ /// The legacy `CSSRule.type` constant (0 for rules without one)
88
+ pub rule_type: u16,
89
+ /// `CSSRule.cssText`
90
+ pub css_text: String,
91
+ /// Whether the rule has a child rule list (`cssRules`)
92
+ pub has_child_rules: bool,
93
+ /// Whether the rule has a `style` declaration block
94
+ pub has_style: bool,
95
+ /// Additional interface-specific string attributes (e.g. `selectorText`)
96
+ pub attributes: Vec<(&'static str, String)>,
97
+ }
98
+
99
+ /// A rule list that CSSOM rule paths can index into
100
+ #[derive(Clone)]
101
+ enum RuleList {
102
+ Css(ServoArc<Locked<CssRules>>),
103
+ Keyframes(ServoArc<Locked<KeyframesRule>>),
104
+ }
105
+
106
+ /// A rule resolved from a CSSOM rule path
107
+ #[derive(Clone)]
108
+ enum RuleHandle {
109
+ Rule(CssRule),
110
+ Keyframe(ServoArc<Locked<Keyframe>>),
111
+ }
112
+
113
+ /// The declarations of a rule (`CSSRule.style`)
114
+ enum DeclarationTarget {
115
+ Block {
116
+ block: ServoArc<Locked<PropertyDeclarationBlock>>,
117
+ rule_type: CssRuleType,
118
+ },
119
+ FontFace(ServoArc<Locked<FontFaceRule>>),
120
+ }
121
+
122
+ fn child_rule_list(rule: &CssRule, guard: &SharedRwLockReadGuard) -> Option<RuleList> {
123
+ let rules = match rule {
124
+ CssRule::Style(r) => r.read_with(guard).rules.clone()?,
125
+ CssRule::Media(r) => r.rules.clone(),
126
+ CssRule::Supports(r) => r.rules.clone(),
127
+ CssRule::Container(r) => r.rules.clone(),
128
+ CssRule::Page(r) => r.read_with(guard).rules.clone(),
129
+ CssRule::Document(r) => r.rules.clone(),
130
+ CssRule::LayerBlock(r) => r.rules.clone(),
131
+ CssRule::Scope(r) => r.rules.clone(),
132
+ CssRule::StartingStyle(r) => r.rules.clone(),
133
+ CssRule::Keyframes(r) => return Some(RuleList::Keyframes(r.clone())),
134
+ _ => return None,
135
+ };
136
+ Some(RuleList::Css(rules))
137
+ }
138
+
139
+ fn declaration_target(
140
+ handle: &RuleHandle,
141
+ guard: &SharedRwLockReadGuard,
142
+ ) -> Option<DeclarationTarget> {
143
+ let (block, rule_type) = match handle {
144
+ RuleHandle::Keyframe(k) => (k.read_with(guard).block.clone(), CssRuleType::Keyframe),
145
+ RuleHandle::Rule(rule) => match rule {
146
+ CssRule::Style(r) => (r.read_with(guard).block.clone(), CssRuleType::Style),
147
+ CssRule::Page(r) => (r.read_with(guard).block.clone(), CssRuleType::Page),
148
+ CssRule::Margin(r) => (r.block.clone(), CssRuleType::Margin),
149
+ CssRule::NestedDeclarations(r) => (
150
+ r.read_with(guard).block.clone(),
151
+ CssRuleType::NestedDeclarations,
152
+ ),
153
+ CssRule::PositionTry(r) => (r.read_with(guard).block.clone(), CssRuleType::PositionTry),
154
+ CssRule::FontFace(r) => return Some(DeclarationTarget::FontFace(r.clone())),
155
+ _ => return None,
156
+ },
157
+ };
158
+ Some(DeclarationTarget::Block { block, rule_type })
159
+ }
160
+
161
+ fn css_rule_interface(rule: &CssRule) -> &'static str {
162
+ match rule {
163
+ CssRule::Style(_) => "CSSStyleRule",
164
+ CssRule::Namespace(_) => "CSSNamespaceRule",
165
+ CssRule::Import(_) => "CSSImportRule",
166
+ CssRule::Media(_) => "CSSMediaRule",
167
+ CssRule::CustomMedia(_) => "CSSCustomMediaRule",
168
+ CssRule::Container(_) => "CSSContainerRule",
169
+ CssRule::FontFace(_) => "CSSFontFaceRule",
170
+ CssRule::FontFeatureValues(_) => "CSSFontFeatureValuesRule",
171
+ CssRule::FontPaletteValues(_) => "CSSFontPaletteValuesRule",
172
+ CssRule::CounterStyle(_) => "CSSCounterStyleRule",
173
+ CssRule::Keyframes(_) => "CSSKeyframesRule",
174
+ CssRule::Margin(_) => "CSSMarginRule",
175
+ CssRule::Supports(_) => "CSSSupportsRule",
176
+ CssRule::Page(_) => "CSSPageRule",
177
+ CssRule::Property(_) => "CSSPropertyRule",
178
+ CssRule::Document(_) => "CSSMozDocumentRule",
179
+ CssRule::LayerBlock(_) => "CSSLayerBlockRule",
180
+ CssRule::LayerStatement(_) => "CSSLayerStatementRule",
181
+ CssRule::Scope(_) => "CSSScopeRule",
182
+ CssRule::StartingStyle(_) => "CSSStartingStyleRule",
183
+ CssRule::AppearanceBase(_) => "CSSAppearanceBaseRule",
184
+ CssRule::PositionTry(_) => "CSSPositionTryRule",
185
+ CssRule::NestedDeclarations(_) => "CSSNestedDeclarations",
186
+ CssRule::ViewTransition(_) => "CSSViewTransitionRule",
187
+ }
188
+ }
189
+
190
+ /// Join the CSS serializations of `items` with `", "`
191
+ fn comma_separated<'a, T: ToCss + 'a>(items: impl IntoIterator<Item = &'a T>) -> String {
192
+ let mut css = CssStringWriter::new();
193
+ for (i, item) in items.into_iter().enumerate() {
194
+ if i > 0 {
195
+ css.push_str(", ");
196
+ }
197
+ let _ = item.to_css(&mut style_traits::CssWriter::new(&mut css));
198
+ }
199
+ css
200
+ }
201
+
202
+ fn rule_attributes(rule: &CssRule, guard: &SharedRwLockReadGuard) -> Vec<(&'static str, String)> {
203
+ match rule {
204
+ CssRule::Style(r) => {
205
+ vec![("selectorText", r.read_with(guard).selectors.to_css_string())]
206
+ }
207
+ CssRule::Namespace(r) => vec![
208
+ ("namespaceURI", r.url.to_string()),
209
+ (
210
+ "prefix",
211
+ r.prefix.as_ref().map(|p| p.to_string()).unwrap_or_default(),
212
+ ),
213
+ ],
214
+ CssRule::Import(r) => {
215
+ let r = r.read_with(guard);
216
+ vec![
217
+ ("href", r.url.as_str().to_string()),
218
+ (
219
+ "media",
220
+ r.stylesheet
221
+ .media(guard)
222
+ .map(|m| m.to_css_string())
223
+ .unwrap_or_default(),
224
+ ),
225
+ ]
226
+ }
227
+ CssRule::Media(r) => vec![(
228
+ "conditionText",
229
+ r.media_queries.read_with(guard).to_css_string(),
230
+ )],
231
+ CssRule::Supports(r) => vec![("conditionText", r.condition.to_css_string())],
232
+ CssRule::Container(r) => vec![("conditionText", r.conditions.to_css_string())],
233
+ CssRule::Page(r) => vec![("selectorText", r.read_with(guard).selectors.to_css_string())],
234
+ CssRule::Keyframes(r) => vec![("name", r.read_with(guard).name.to_css_string())],
235
+ CssRule::LayerBlock(r) => vec![(
236
+ "name",
237
+ r.name
238
+ .as_ref()
239
+ .map(|n| n.to_css_string())
240
+ .unwrap_or_default(),
241
+ )],
242
+ CssRule::LayerStatement(r) => vec![("nameList", comma_separated(r.names.iter()))],
243
+ CssRule::Property(r) => {
244
+ let mut attrs = vec![("name", r.name.to_css_string())];
245
+ for (attr, id) in [
246
+ ("syntax", style::properties::property::DescriptorId::Syntax),
247
+ (
248
+ "inherits",
249
+ style::properties::property::DescriptorId::Inherits,
250
+ ),
251
+ (
252
+ "initialValue",
253
+ style::properties::property::DescriptorId::InitialValue,
254
+ ),
255
+ ] {
256
+ let mut css = CssStringWriter::new();
257
+ let _ = r.descriptors.get(id, &mut css);
258
+ attrs.push((attr, css));
259
+ }
260
+ attrs
261
+ }
262
+ CssRule::CounterStyle(r) => vec![("name", r.read_with(guard).name().to_css_string())],
263
+ CssRule::FontFeatureValues(r) => {
264
+ vec![("fontFamily", comma_separated(r.family_names.iter()))]
265
+ }
266
+ CssRule::FontPaletteValues(r) => vec![
267
+ ("name", r.name.to_css_string()),
268
+ ("fontFamily", comma_separated(r.family_names.iter())),
269
+ (
270
+ "basePalette",
271
+ r.base_palette
272
+ .as_ref()
273
+ .map(|b| b.to_css_string())
274
+ .unwrap_or_default(),
275
+ ),
276
+ ("overrideColors", comma_separated(r.override_colors.iter())),
277
+ ],
278
+ CssRule::Margin(r) => vec![("name", format!("{:?}", r.rule_type))],
279
+ CssRule::PositionTry(r) => vec![("name", r.read_with(guard).name.to_css_string())],
280
+ _ => Vec::new(),
281
+ }
282
+ }
283
+
284
+ impl BaseDocument {
285
+ /// The owner nodes (`<style>` / `<link>` elements) of the document's author
286
+ /// stylesheets (`document.styleSheets`).
287
+ ///
288
+ /// Ordered by node id, which matches document order for stylesheets
289
+ /// created while parsing (and is the order the stylist uses too, see
290
+ /// `add_stylesheet_for_node`).
291
+ pub fn stylesheet_owner_nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
292
+ self.nodes_to_stylesheet.keys().copied()
293
+ }
294
+
295
+ /// Whether `node_id` currently owns a stylesheet
296
+ pub fn node_has_stylesheet(&self, node_id: NodeId) -> bool {
297
+ self.nodes_to_stylesheet.contains_key(&node_id)
298
+ }
299
+
300
+ /// A counter which changes whenever the set of stylesheets (or the
301
+ /// stylesheet object owned by some node) changes. CSSOM wrappers use it to
302
+ /// detect that rule data cached on the script side is stale.
303
+ pub fn stylesheet_generation(&self) -> u64 {
304
+ self.stylesheet_generation
305
+ }
306
+
307
+ /// Whether the stylesheet owned by `node_id` has been disabled through the
308
+ /// CSSOM (`CSSStyleSheet.disabled` / `HTMLStyleElement.disabled` /
309
+ /// `HTMLLinkElement.disabled`). `false` if the node owns no stylesheet.
310
+ pub fn stylesheet_disabled(&self, node_id: NodeId) -> bool {
311
+ self.nodes_to_stylesheet
312
+ .get(&node_id)
313
+ .is_some_and(|sheet| sheet.0.disabled())
314
+ }
315
+
316
+ /// Set the disabled flag of the stylesheet owned by `node_id`. A disabled
317
+ /// stylesheet stays associated with its owner node but its rules no longer
318
+ /// apply to the document.
319
+ ///
320
+ /// Returns `false` if the node owns no stylesheet.
321
+ pub fn set_stylesheet_disabled(&mut self, node_id: NodeId, disabled: bool) -> bool {
322
+ let Some(sheet) = self.nodes_to_stylesheet.get(&node_id) else {
323
+ return false;
324
+ };
325
+ if sheet.0.set_disabled(disabled) {
326
+ self.stylist
327
+ .force_stylesheet_origins_dirty(OriginSet::all());
328
+ }
329
+ true
330
+ }
331
+
332
+ fn stylesheet_loader(&self) -> StylesheetLoader {
333
+ StylesheetLoader {
334
+ tx: self.tx.clone(),
335
+ doc_id: self.id(),
336
+ net_provider: self.net_provider.clone(),
337
+ shell_provider: self.shell_provider.clone(),
338
+ abort_signal: self.abort_signal.clone(),
339
+ import_depth: 0,
340
+ }
341
+ }
342
+
343
+ /// Resolve `path` to the rule it denotes. The rule's ancestors (outermost
344
+ /// first) are passed to `on_ancestor` as they are traversed.
345
+ fn resolve_rule(
346
+ sheet: &DocumentStyleSheet,
347
+ guard: &SharedRwLockReadGuard,
348
+ path: &[usize],
349
+ on_ancestor: impl FnMut(&CssRule),
350
+ ) -> Option<RuleHandle> {
351
+ let (index, parent_path) = path.split_last()?;
352
+ let list = Self::resolve_rule_list(sheet, guard, parent_path, on_ancestor)?;
353
+ Some(match list {
354
+ RuleList::Css(rules) => RuleHandle::Rule(rules.read_with(guard).0.get(*index)?.clone()),
355
+ RuleList::Keyframes(keyframes) => {
356
+ RuleHandle::Keyframe(keyframes.read_with(guard).keyframes.get(*index)?.clone())
357
+ }
358
+ })
359
+ }
360
+
361
+ /// Resolve `path` to the rule list owned by the rule it denotes (or the
362
+ /// stylesheet's top-level rule list for an empty path). The ancestor rules
363
+ /// of that list (outermost first, including the rule at `path` itself) are
364
+ /// passed to `on_ancestor` as they are traversed.
365
+ fn resolve_rule_list(
366
+ sheet: &DocumentStyleSheet,
367
+ guard: &SharedRwLockReadGuard,
368
+ path: &[usize],
369
+ mut on_ancestor: impl FnMut(&CssRule),
370
+ ) -> Option<RuleList> {
371
+ let mut list = RuleList::Css(sheet.contents(guard).rules.clone());
372
+ for &index in path {
373
+ let RuleList::Css(rules) = &list else {
374
+ return None;
375
+ };
376
+ let rules = rules.read_with(guard);
377
+ let rule = rules.0.get(index)?;
378
+ let child_list = child_rule_list(rule, guard)?;
379
+ on_ancestor(rule);
380
+ list = child_list;
381
+ }
382
+ Some(list)
383
+ }
384
+
385
+ /// [`Self::resolve_rule_list`], also collecting the ancestor rules
386
+ fn resolve_rule_list_with_ancestors(
387
+ sheet: &DocumentStyleSheet,
388
+ guard: &SharedRwLockReadGuard,
389
+ path: &[usize],
390
+ ) -> Option<(Vec<CssRule>, RuleList)> {
391
+ let mut ancestors = Vec::with_capacity(path.len());
392
+ let list =
393
+ Self::resolve_rule_list(sheet, guard, path, |rule| ancestors.push(rule.clone()))?;
394
+ Some((ancestors, list))
395
+ }
396
+
397
+ /// The number of rules in the rule list at `path` (`CSSRuleList.length`)
398
+ pub fn stylesheet_rule_count(&self, node_id: NodeId, path: &[usize]) -> Option<usize> {
399
+ let sheet = self.nodes_to_stylesheet.get(&node_id)?;
400
+ let guard = self.guard.read();
401
+ let list = Self::resolve_rule_list(sheet, &guard, path, |_| {})?;
402
+ Some(match list {
403
+ RuleList::Css(rules) => rules.read_with(&guard).0.len(),
404
+ RuleList::Keyframes(keyframes) => keyframes.read_with(&guard).keyframes.len(),
405
+ })
406
+ }
407
+
408
+ /// The CSSOM-visible attributes of the rule at `path`
409
+ pub fn stylesheet_rule_info(&self, node_id: NodeId, path: &[usize]) -> Option<CssRuleInfo> {
410
+ let sheet = self.nodes_to_stylesheet.get(&node_id)?;
411
+ let guard = self.guard.read();
412
+ let handle = Self::resolve_rule(sheet, &guard, path, |_| {})?;
413
+ let mut css_text = CssStringWriter::new();
414
+ let has_style = declaration_target(&handle, &guard).is_some();
415
+ Some(match handle {
416
+ RuleHandle::Rule(rule) => {
417
+ let _ = rule.to_css(&guard, &mut css_text);
418
+ CssRuleInfo {
419
+ interface: css_rule_interface(&rule),
420
+ rule_type: rule.rule_type() as u16,
421
+ css_text,
422
+ has_child_rules: child_rule_list(&rule, &guard).is_some(),
423
+ has_style,
424
+ attributes: rule_attributes(&rule, &guard),
425
+ }
426
+ }
427
+ RuleHandle::Keyframe(keyframe) => {
428
+ let keyframe = keyframe.read_with(&guard);
429
+ let _ = keyframe.to_css(&guard, &mut css_text);
430
+ CssRuleInfo {
431
+ interface: "CSSKeyframeRule",
432
+ rule_type: CssRuleType::Keyframe as u16,
433
+ css_text,
434
+ has_child_rules: false,
435
+ has_style,
436
+ attributes: vec![("keyText", keyframe.selector.to_css_string())],
437
+ }
438
+ }
439
+ })
440
+ }
441
+
442
+ /// Insert a rule parsed from `rule` at `index` in the rule list at `path`
443
+ /// (`CSSStyleSheet.insertRule` / `CSSGroupingRule.insertRule` /
444
+ /// `CSSKeyframesRule.appendRule`). Returns the index of the inserted rule.
445
+ pub fn stylesheet_insert_rule(
446
+ &mut self,
447
+ node_id: NodeId,
448
+ path: &[usize],
449
+ rule: &str,
450
+ index: usize,
451
+ ) -> Result<usize, CssomError> {
452
+ let sheet = self
453
+ .nodes_to_stylesheet
454
+ .get(&node_id)
455
+ .ok_or(CssomError::NotFound)?
456
+ .clone();
457
+ let loader = self.stylesheet_loader();
458
+ let lock = &self.guard;
459
+
460
+ let (ancestors, list) = {
461
+ let guard = lock.read();
462
+ Self::resolve_rule_list_with_ancestors(&sheet, &guard, path)
463
+ .ok_or(CssomError::NotFound)?
464
+ };
465
+
466
+ let (new_rule, index) = match &list {
467
+ RuleList::Css(rules) => {
468
+ let new_rule = {
469
+ let guard = lock.read();
470
+ let contents = sheet.contents(&guard);
471
+ let containing_rule_types =
472
+ ancestors
473
+ .iter()
474
+ .fold(CssRuleTypes::default(), |types, rule| {
475
+ CssRuleTypes::from_bits(types.bits() | rule.rule_type().bit())
476
+ });
477
+ let parse_relative_rule_type = ancestors.last().map(|rule| rule.rule_type());
478
+ rules.read_with(&guard).parse_rule_for_insert(
479
+ lock,
480
+ rule,
481
+ contents,
482
+ index,
483
+ containing_rule_types,
484
+ parse_relative_rule_type,
485
+ Some(&loader),
486
+ AllowImportRules::Yes,
487
+ )?
488
+ };
489
+ {
490
+ let mut guard = lock.write();
491
+ rules
492
+ .write_with(&mut guard)
493
+ .0
494
+ .insert(index, new_rule.clone());
495
+ }
496
+ (new_rule, index)
497
+ }
498
+ RuleList::Keyframes(keyframes) => {
499
+ let keyframe = {
500
+ let guard = lock.read();
501
+ let contents = sheet.contents(&guard);
502
+ Keyframe::parse(rule, contents, lock).map_err(|_| CssomError::Syntax)?
503
+ };
504
+ let index = {
505
+ let mut guard = lock.write();
506
+ let keyframes = &mut keyframes.write_with(&mut guard).keyframes;
507
+ keyframes.push(keyframe);
508
+ keyframes.len() - 1
509
+ };
510
+ (CssRule::Keyframes(keyframes.clone()), index)
511
+ }
512
+ };
513
+
514
+ let guard = lock.read();
515
+ if let CssRule::FontFace(_) = &new_rule {
516
+ crate::net::fetch_font_face_rules(
517
+ std::iter::once(&new_rule),
518
+ self.tx.clone(),
519
+ self.id(),
520
+ Some(node_id),
521
+ &self.net_provider,
522
+ &self.shell_provider,
523
+ &guard,
524
+ self.abort_signal.as_ref(),
525
+ );
526
+ }
527
+ let (change_kind, ancestors) = match &list {
528
+ RuleList::Css(_) => (RuleChangeKind::Insertion, ancestors.as_slice()),
529
+ // The keyframes rule itself changed; its ancestors exclude it
530
+ RuleList::Keyframes(_) => (
531
+ RuleChangeKind::Generic,
532
+ &ancestors[..ancestors.len().saturating_sub(1)],
533
+ ),
534
+ };
535
+ let ancestor_refs: Vec<CssRuleRef> = ancestors.iter().map(CssRuleRef::from).collect();
536
+ self.stylist
537
+ .rule_changed(&sheet, &new_rule, &guard, change_kind, &ancestor_refs);
538
+ Ok(index)
539
+ }
540
+
541
+ /// Remove the rule at `index` from the rule list at `path`
542
+ /// (`CSSStyleSheet.deleteRule` / `CSSGroupingRule.deleteRule` /
543
+ /// `CSSKeyframesRule.deleteRule`).
544
+ pub fn stylesheet_delete_rule(
545
+ &mut self,
546
+ node_id: NodeId,
547
+ path: &[usize],
548
+ index: usize,
549
+ ) -> Result<(), CssomError> {
550
+ let sheet = self
551
+ .nodes_to_stylesheet
552
+ .get(&node_id)
553
+ .ok_or(CssomError::NotFound)?
554
+ .clone();
555
+ let lock = &self.guard;
556
+
557
+ let (ancestors, list) = {
558
+ let guard = lock.read();
559
+ Self::resolve_rule_list_with_ancestors(&sheet, &guard, path)
560
+ .ok_or(CssomError::NotFound)?
561
+ };
562
+
563
+ let (removed_rule, change_kind, ancestors) = match &list {
564
+ RuleList::Css(rules) => {
565
+ let mut guard = lock.write();
566
+ let rules = rules.write_with(&mut guard);
567
+ let removed = rules.0.get(index).cloned().ok_or(CssomError::IndexSize)?;
568
+ rules.remove_rule(index)?;
569
+ (removed, RuleChangeKind::Removal, ancestors.as_slice())
570
+ }
571
+ RuleList::Keyframes(keyframes) => {
572
+ let mut guard = lock.write();
573
+ let list = &mut keyframes.write_with(&mut guard).keyframes;
574
+ if index >= list.len() {
575
+ return Err(CssomError::IndexSize);
576
+ }
577
+ list.remove(index);
578
+ (
579
+ CssRule::Keyframes(keyframes.clone()),
580
+ RuleChangeKind::Generic,
581
+ &ancestors[..ancestors.len().saturating_sub(1)],
582
+ )
583
+ }
584
+ };
585
+
586
+ let guard = lock.read();
587
+ let ancestor_refs: Vec<CssRuleRef> = ancestors.iter().map(CssRuleRef::from).collect();
588
+ self.stylist
589
+ .rule_changed(&sheet, &removed_rule, &guard, change_kind, &ancestor_refs);
590
+ Ok(())
591
+ }
592
+
593
+ /// The declarations of the rule at `path`, for read-only access
594
+ fn rule_declaration_target(
595
+ &self,
596
+ node_id: NodeId,
597
+ path: &[usize],
598
+ guard: &SharedRwLockReadGuard,
599
+ ) -> Option<DeclarationTarget> {
600
+ let sheet = self.nodes_to_stylesheet.get(&node_id)?;
601
+ let handle = Self::resolve_rule(sheet, guard, path, |_| {})?;
602
+ declaration_target(&handle, guard)
603
+ }
604
+
605
+ /// The declarations of the rule at `path`, along with what is needed to
606
+ /// notify the stylist after mutating them: the stylesheet, the rule to
607
+ /// report as changed and its ancestors.
608
+ fn rule_declaration_target_mut(
609
+ &self,
610
+ node_id: NodeId,
611
+ path: &[usize],
612
+ ) -> Option<(DocumentStyleSheet, Vec<CssRule>, CssRule, DeclarationTarget)> {
613
+ let sheet = self.nodes_to_stylesheet.get(&node_id)?.clone();
614
+ let guard = self.guard.read();
615
+ let mut ancestors = Vec::with_capacity(path.len());
616
+ let handle = Self::resolve_rule(&sheet, &guard, path, |rule| ancestors.push(rule.clone()))?;
617
+ let target = declaration_target(&handle, &guard)?;
618
+ // For keyframes, the rule to report as changed is the owning
619
+ // `@keyframes` rule
620
+ let (changed_rule, ancestors) = match handle {
621
+ RuleHandle::Rule(rule) => (rule, ancestors),
622
+ RuleHandle::Keyframe(_) => {
623
+ let mut ancestors = ancestors;
624
+ let rule = ancestors.pop()?;
625
+ (rule, ancestors)
626
+ }
627
+ };
628
+ Some((sheet, ancestors, changed_rule, target))
629
+ }
630
+
631
+ /// The serialized declarations of the rule at `path` (`CSSRule.style.cssText`)
632
+ pub fn stylesheet_rule_style_css_text(
633
+ &self,
634
+ node_id: NodeId,
635
+ path: &[usize],
636
+ ) -> Option<String> {
637
+ let guard = self.guard.read();
638
+ let target = self.rule_declaration_target(node_id, path, &guard)?;
639
+ let mut css = CssStringWriter::new();
640
+ match target {
641
+ DeclarationTarget::Block { block, .. } => {
642
+ let _ = block.read_with(&guard).to_css(&mut css);
643
+ }
644
+ DeclarationTarget::FontFace(rule) => {
645
+ let _ = rule
646
+ .read_with(&guard)
647
+ .descriptors
648
+ .to_css(&mut style_traits::CssWriter::new(&mut css));
649
+ // `Descriptors::to_css` emits a trailing space after each declaration
650
+ let trimmed = css.trim_end().len();
651
+ css.truncate(trimmed);
652
+ }
653
+ }
654
+ Some(css)
655
+ }
656
+
657
+ /// The names of the declarations of the rule at `path`, in order
658
+ /// (`CSSRule.style.length` / `.item()`)
659
+ pub fn stylesheet_rule_style_property_names(
660
+ &self,
661
+ node_id: NodeId,
662
+ path: &[usize],
663
+ ) -> Option<Vec<String>> {
664
+ let guard = self.guard.read();
665
+ let target = self.rule_declaration_target(node_id, path, &guard)?;
666
+ Some(match target {
667
+ DeclarationTarget::Block { block, .. } => block
668
+ .read_with(&guard)
669
+ .declarations()
670
+ .iter()
671
+ .map(|decl| decl.id().name().into_owned())
672
+ .collect(),
673
+ DeclarationTarget::FontFace(rule) => {
674
+ let descriptors = &rule.read_with(&guard).descriptors;
675
+ (0..descriptors.len())
676
+ .filter_map(|i| descriptors.at(i))
677
+ .map(|id| id.name().to_string())
678
+ .collect()
679
+ }
680
+ })
681
+ }
682
+
683
+ /// The serialized value of `property` in the declarations of the rule at
684
+ /// `path`, and whether it is `!important`
685
+ /// (`CSSRule.style.getPropertyValue()` / `.getPropertyPriority()`).
686
+ /// Returns an empty string for unset or unknown properties.
687
+ pub fn stylesheet_rule_style_get_property(
688
+ &self,
689
+ node_id: NodeId,
690
+ path: &[usize],
691
+ property: &str,
692
+ ) -> Option<(String, bool)> {
693
+ let guard = self.guard.read();
694
+ let target = self.rule_declaration_target(node_id, path, &guard)?;
695
+ let mut css = CssStringWriter::new();
696
+ let important = match target {
697
+ DeclarationTarget::Block { block, .. } => {
698
+ let Ok(property_id) = PropertyId::parse_enabled_for_all_content(property) else {
699
+ return Some((String::new(), false));
700
+ };
701
+ let block = block.read_with(&guard);
702
+ let _ = block.property_value_to_css(&property_id, &mut css);
703
+ block.property_priority(&property_id).important()
704
+ }
705
+ DeclarationTarget::FontFace(rule) => {
706
+ let Ok(id) = FontFaceDescriptorId::from_ident(property) else {
707
+ return Some((String::new(), false));
708
+ };
709
+ let _ = rule.read_with(&guard).descriptors.get(id, &mut css);
710
+ false
711
+ }
712
+ };
713
+ Some((css, important))
714
+ }
715
+
716
+ /// Set `property` to `value` in the declarations of the rule at `path`
717
+ /// (`CSSRule.style.setProperty()`). Invalid declarations are ignored, per
718
+ /// CSSOM. An empty `value` removes the property.
719
+ pub fn stylesheet_rule_style_set_property(
720
+ &mut self,
721
+ node_id: NodeId,
722
+ path: &[usize],
723
+ property: &str,
724
+ value: &str,
725
+ important: bool,
726
+ ) -> Result<(), CssomError> {
727
+ if value.trim().is_empty() {
728
+ self.stylesheet_rule_style_remove_property(node_id, path, property)?;
729
+ return Ok(());
730
+ }
731
+ let (sheet, ancestors, rule, target) = self
732
+ .rule_declaration_target_mut(node_id, path)
733
+ .ok_or(CssomError::NotFound)?;
734
+ let url_data = self.url.url_extra_data();
735
+ let lock = &self.guard;
736
+
737
+ let change_kind = match target {
738
+ DeclarationTarget::Block { block, rule_type } => {
739
+ let Ok(property_id) = PropertyId::parse_enabled_for_all_content(property) else {
740
+ return Ok(());
741
+ };
742
+ let mut source = SourcePropertyDeclaration::default();
743
+ if parse_one_declaration_into(
744
+ &mut source,
745
+ property_id,
746
+ value,
747
+ Origin::Author,
748
+ &url_data,
749
+ None,
750
+ ParsingMode::DEFAULT,
751
+ QuirksMode::NoQuirks,
752
+ rule_type,
753
+ )
754
+ .is_err()
755
+ {
756
+ return Ok(());
757
+ }
758
+ let importance = if important {
759
+ Importance::Important
760
+ } else {
761
+ Importance::Normal
762
+ };
763
+ let mut guard = lock.write();
764
+ block
765
+ .write_with(&mut guard)
766
+ .extend(source.drain(), importance);
767
+ match rule_type {
768
+ CssRuleType::PositionTry => RuleChangeKind::PositionTryDeclarations,
769
+ _ => RuleChangeKind::StyleRuleDeclarations,
770
+ }
771
+ }
772
+ DeclarationTarget::FontFace(font_face) => {
773
+ let Ok(id) = FontFaceDescriptorId::from_ident(property) else {
774
+ return Ok(());
775
+ };
776
+ let context = ParserContext::new(
777
+ Origin::Author,
778
+ &url_data,
779
+ Some(CssRuleType::FontFace),
780
+ ParsingMode::DEFAULT,
781
+ QuirksMode::NoQuirks,
782
+ Default::default(),
783
+ None,
784
+ None,
785
+ Default::default(),
786
+ );
787
+ let mut parser = Parser::new(value);
788
+ let mut guard = lock.write();
789
+ let descriptors = &mut font_face.write_with(&mut guard).descriptors;
790
+ if descriptors.set(id, &context, &mut parser).is_err() {
791
+ return Ok(());
792
+ }
793
+ RuleChangeKind::Generic
794
+ }
795
+ };
796
+
797
+ let guard = lock.read();
798
+ let ancestor_refs: Vec<CssRuleRef> = ancestors.iter().map(CssRuleRef::from).collect();
799
+ self.stylist
800
+ .rule_changed(&sheet, &rule, &guard, change_kind, &ancestor_refs);
801
+ Ok(())
802
+ }
803
+
804
+ /// Remove `property` from the declarations of the rule at `path`
805
+ /// (`CSSRule.style.removeProperty()`). Returns the removed property's
806
+ /// previous serialized value.
807
+ pub fn stylesheet_rule_style_remove_property(
808
+ &mut self,
809
+ node_id: NodeId,
810
+ path: &[usize],
811
+ property: &str,
812
+ ) -> Result<String, CssomError> {
813
+ let (sheet, ancestors, rule, target) = self
814
+ .rule_declaration_target_mut(node_id, path)
815
+ .ok_or(CssomError::NotFound)?;
816
+ let lock = &self.guard;
817
+ let mut removed_value = CssStringWriter::new();
818
+
819
+ let change_kind = match target {
820
+ DeclarationTarget::Block { block, rule_type } => {
821
+ let Ok(property_id) = PropertyId::parse_enabled_for_all_content(property) else {
822
+ return Ok(removed_value);
823
+ };
824
+ let mut guard = lock.write();
825
+ let block = block.write_with(&mut guard);
826
+ let _ = block.property_value_to_css(&property_id, &mut removed_value);
827
+ let Some(first_declaration) = block.first_declaration_to_remove(&property_id)
828
+ else {
829
+ return Ok(removed_value);
830
+ };
831
+ block.remove_property(&property_id, first_declaration);
832
+ match rule_type {
833
+ CssRuleType::PositionTry => RuleChangeKind::PositionTryDeclarations,
834
+ _ => RuleChangeKind::StyleRuleDeclarations,
835
+ }
836
+ }
837
+ DeclarationTarget::FontFace(font_face) => {
838
+ let Ok(id) = FontFaceDescriptorId::from_ident(property) else {
839
+ return Ok(removed_value);
840
+ };
841
+ let mut guard = lock.write();
842
+ let descriptors = &mut font_face.write_with(&mut guard).descriptors;
843
+ let _ = descriptors.get(id, &mut removed_value);
844
+ if !descriptors.remove(id) {
845
+ return Ok(removed_value);
846
+ }
847
+ RuleChangeKind::Generic
848
+ }
849
+ };
850
+
851
+ let guard = lock.read();
852
+ let ancestor_refs: Vec<CssRuleRef> = ancestors.iter().map(CssRuleRef::from).collect();
853
+ self.stylist
854
+ .rule_changed(&sheet, &rule, &guard, change_kind, &ancestor_refs);
855
+ Ok(removed_value)
856
+ }
857
+
858
+ /// Replace the selectors of the style rule at `path` with those parsed
859
+ /// from `selectors` (`CSSStyleRule.selectorText` setter). Per CSSOM, an
860
+ /// invalid selector list leaves the rule unchanged (without throwing).
861
+ pub fn stylesheet_rule_set_selector_text(
862
+ &mut self,
863
+ node_id: NodeId,
864
+ path: &[usize],
865
+ selectors: &str,
866
+ ) -> Result<(), CssomError> {
867
+ let sheet = self
868
+ .nodes_to_stylesheet
869
+ .get(&node_id)
870
+ .ok_or(CssomError::NotFound)?
871
+ .clone();
872
+ let lock = &self.guard;
873
+
874
+ let (ancestors, style_rule) = {
875
+ let guard = lock.read();
876
+ let mut ancestors = Vec::with_capacity(path.len());
877
+ let handle =
878
+ Self::resolve_rule(&sheet, &guard, path, |rule| ancestors.push(rule.clone()))
879
+ .ok_or(CssomError::NotFound)?;
880
+ let RuleHandle::Rule(CssRule::Style(style_rule)) = handle else {
881
+ return Err(CssomError::NotSupported);
882
+ };
883
+ (ancestors, style_rule)
884
+ };
885
+
886
+ // Nested style rules take relative selectors (`> .a`), resolved
887
+ // against the innermost enclosing style rule or `@scope`, mirroring
888
+ // stylo's `NestingContext`.
889
+ let parse_relative = ancestors
890
+ .iter()
891
+ .rev()
892
+ .find_map(|rule| match rule.rule_type() {
893
+ CssRuleType::Style => Some(ParseRelative::ForNesting),
894
+ CssRuleType::Scope => Some(ParseRelative::ForScope),
895
+ _ => None,
896
+ })
897
+ .unwrap_or(ParseRelative::No);
898
+
899
+ let new_selectors = {
900
+ let guard = lock.read();
901
+ let contents = sheet.contents(&guard);
902
+ let selector_parser = SelectorParser {
903
+ stylesheet_origin: contents.origin,
904
+ namespaces: &contents.namespaces,
905
+ url_data: &contents.url_data,
906
+ for_supports_rule: false,
907
+ };
908
+ let mut parser = Parser::new(selectors);
909
+ let Ok(new_selectors) =
910
+ SelectorList::parse(&selector_parser, &mut parser, parse_relative)
911
+ else {
912
+ return Ok(());
913
+ };
914
+ new_selectors
915
+ };
916
+
917
+ {
918
+ let mut guard = lock.write();
919
+ style_rule.write_with(&mut guard).selectors = new_selectors;
920
+ }
921
+
922
+ let guard = lock.read();
923
+ let rule = CssRule::Style(style_rule);
924
+ let ancestor_refs: Vec<CssRuleRef> = ancestors.iter().map(CssRuleRef::from).collect();
925
+ self.stylist.rule_changed(
926
+ &sheet,
927
+ &rule,
928
+ &guard,
929
+ RuleChangeKind::Generic,
930
+ &ancestor_refs,
931
+ );
932
+ Ok(())
933
+ }
934
+
935
+ /// Replace the declarations of the rule at `path` with those parsed from
936
+ /// `css` (`CSSRule.style.cssText` setter).
937
+ pub fn stylesheet_rule_style_set_css_text(
938
+ &mut self,
939
+ node_id: NodeId,
940
+ path: &[usize],
941
+ css: &str,
942
+ ) -> Result<(), CssomError> {
943
+ let (sheet, ancestors, rule, target) = self
944
+ .rule_declaration_target_mut(node_id, path)
945
+ .ok_or(CssomError::NotFound)?;
946
+ let DeclarationTarget::Block { block, rule_type } = target else {
947
+ return Err(CssomError::NotSupported);
948
+ };
949
+ let new_block = style::properties::declaration_block::parse_style_attribute(
950
+ css,
951
+ &self.url.url_extra_data(),
952
+ None,
953
+ QuirksMode::NoQuirks,
954
+ rule_type,
955
+ );
956
+ let lock = &self.guard;
957
+ {
958
+ let mut guard = lock.write();
959
+ *block.write_with(&mut guard) = new_block;
960
+ }
961
+ let guard = lock.read();
962
+ let ancestor_refs: Vec<CssRuleRef> = ancestors.iter().map(CssRuleRef::from).collect();
963
+ let change_kind = match rule_type {
964
+ CssRuleType::PositionTry => RuleChangeKind::PositionTryDeclarations,
965
+ _ => RuleChangeKind::StyleRuleDeclarations,
966
+ };
967
+ self.stylist
968
+ .rule_changed(&sheet, &rule, &guard, change_kind, &ancestor_refs);
969
+ Ok(())
970
+ }
971
+ }