sheetom 0.1.0-rc.9 → 0.1.1

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.
package/README.md CHANGED
@@ -64,6 +64,31 @@ Constructed sheets strip `@import` during replacement. `parseStyleSheet()` is a
64
64
  SheetOM extension for existing regular stylesheets and preserves valid imports
65
65
  without fetching them.
66
66
 
67
+ Compilers can apply all known mutations for one rule with one engine crossing
68
+ and still receive one acceptance result per operation:
69
+
70
+ ```ts
71
+ function applyCompilerDeclarations(rule: CSSStyleRule, dynamicWidth: string) {
72
+ const results = rule.style.applyMutations([
73
+ { kind: "set", property: "display", value: "grid" },
74
+ { kind: "set", property: "width", value: dynamicWidth },
75
+ { kind: "remove", property: "margin-left" },
76
+ ]);
77
+
78
+ for (const result of results) {
79
+ if (result.kind === "set" && !result.accepted) {
80
+ console.warn(result.diagnostic);
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ Operations run in order through the same validation and shorthand/longhand
87
+ state machine as individual calls. Group one batch per declaration block;
88
+ never combine values into declaration text. Batch results already include
89
+ rejection diagnostics, so high-volume callers can leave the sheet diagnostic
90
+ queue disabled unless they also need diagnostics from other APIs.
91
+
67
92
  ## Browser recovery, safe output
68
93
 
69
94
  Browsers accept some CSS using end-of-input recovery and expose the incomplete
@@ -94,6 +119,13 @@ sheet.serialize();
94
119
  | `style.cssText` | Browser-compatible declaration text, including measured recovery quirks. |
95
120
  | `rule.cssText` | Browser-compatible text for the current rule. |
96
121
  | `sheet.serialize()` | Deterministic, reparsable CSS for a complete stylesheet. |
122
+ | `sheet.serializeStrict()` | Exact-only output that rejects CSSOM states CSS text cannot represent faithfully. |
123
+
124
+ Pending-substitution shorthands survive later longhand edits in reparsable
125
+ output. If an imperative priority combination cannot be represented exactly,
126
+ `serialize()` emits a local best-effort result and an opt-in structured
127
+ diagnostic rather than blocking the whole stylesheet. Use `serializeStrict()`
128
+ when approximation is unacceptable.
97
129
 
98
130
  Invalid property values and priorities are atomic no-ops: the previous state
99
131
  survives unchanged. Static shorthands are stored as canonical longhands, so
package/dist/index.cjs CHANGED
@@ -28,9 +28,9 @@ let node_url = require("node:url");
28
28
  //#endregion
29
29
  //#region src/internal/engine-binding.ts
30
30
  const expectedEngineAbiIdentity = Object.freeze({
31
- abiVersion: 1,
32
- sheetomVersion: "0.1.0-rc.9",
33
- syntaxEngineSetSha256: "2af7ec76ba46a3f20f74a90172a708afab5dcb20f45b658a4d975caed76dc855"
31
+ abiVersion: 4,
32
+ sheetomVersion: "0.1.1",
33
+ syntaxEngineSetSha256: "a0f10d0c1f0fe9e5bac9002e8ee548e7b5f6e2c7add1af3d953ae718d25cb37b"
34
34
  });
35
35
  var SheetOMEngineBindingError = class extends Error {
36
36
  code;
@@ -65,7 +65,7 @@ function loadBinding() {
65
65
  validateEngineBindingIdentity(addon);
66
66
  return {
67
67
  ...addon,
68
- createDeclarationState: (context, ...budget) => new addon.NativeDeclarationState(context, ...budget)
68
+ createDeclarationState: (context, ...arguments_) => new addon.NativeDeclarationState(context, ...arguments_)
69
69
  };
70
70
  }
71
71
  //#endregion
@@ -114,18 +114,54 @@ function limitValue(value, name, fallback, maximum = maximumUnsignedLong) {
114
114
  throw new RangeError(`${name} must be an integer between 0 and ${maximum}`);
115
115
  }
116
116
  //#endregion
117
+ //#region src/serialization-error.ts
118
+ /** A reparsable stylesheet cannot faithfully represent the current CSSOM state. */
119
+ var SheetOMSerializationError = class extends Error {
120
+ code;
121
+ shorthand;
122
+ conflictingLonghands;
123
+ constructor(shorthand, conflictingLonghands, options) {
124
+ super(`SheetOM cannot serialize the pending ${shorthand} shorthand without changing its semantics.`, options);
125
+ this.name = "SheetOMSerializationError";
126
+ this.code = "UNREPRESENTABLE_PENDING_SHORTHAND";
127
+ this.shorthand = shorthand;
128
+ this.conflictingLonghands = Object.freeze([...conflictingLonghands]);
129
+ }
130
+ };
131
+ const unrepresentablePendingShorthandPattern = /^SHEETOM_UNREPRESENTABLE_PENDING_SHORTHAND: shorthand=([^;]+); conflicting=([a-z0-9,-]+)$/u;
132
+ function serializationErrorFrom(error) {
133
+ if (!(error instanceof Error)) return null;
134
+ const match = unrepresentablePendingShorthandPattern.exec(error.message);
135
+ if (!match) return null;
136
+ const shorthand = match[1];
137
+ const conflicts = match[2];
138
+ if (!shorthand || !conflicts) return null;
139
+ return new SheetOMSerializationError(shorthand, conflicts.split(","), { cause: error });
140
+ }
141
+ function rethrowSerializationError(error) {
142
+ const serializationError = serializationErrorFrom(error);
143
+ if (serializationError) throw serializationError;
144
+ }
145
+ //#endregion
117
146
  //#region src/internal/native-declaration-block.ts
118
147
  /** Thin JS ownership boundary around the Rust declaration state machine. */
119
148
  var NativeDeclarationBlock = class {
120
149
  #state;
121
150
  #reportDiagnostic;
151
+ #reportSerializationDiagnostic;
122
152
  #reservedNestingDepth;
123
153
  #observableCache;
124
154
  #serializationCache = /* @__PURE__ */ new Map();
125
- constructor(reportDiagnostic, context = "style", resourceBudget = defaultResourceBudget, reservedNestingDepth = () => 0) {
155
+ constructor(reportDiagnostic, reportSerializationDiagnostic, context = "style", resourceBudget = defaultResourceBudget, reservedNestingDepth = () => 0, initialCssText) {
126
156
  this.#reportDiagnostic = reportDiagnostic;
157
+ this.#reportSerializationDiagnostic = reportSerializationDiagnostic;
127
158
  this.#reservedNestingDepth = reservedNestingDepth;
128
- this.#state = engineBinding.createDeclarationState(context, ...nativeBudgetArguments(resourceBudget));
159
+ try {
160
+ this.#state = engineBinding.createDeclarationState(context, ...nativeBudgetArguments(resourceBudget), initialCssText);
161
+ } catch (error) {
162
+ rethrowResourceBudgetError(error);
163
+ throw error;
164
+ }
129
165
  }
130
166
  get cssText() {
131
167
  this.#observableCache ??= this.#state.cssText;
@@ -155,9 +191,59 @@ var NativeDeclarationBlock = class {
155
191
  this.#invalidateSerialization();
156
192
  return;
157
193
  }
158
- const normalizedName = name.startsWith("--") ? name : name.toLowerCase();
159
- const [code, input] = outcome === "invalid-priority" ? ["INVALID_PRIORITY", priority] : outcome === "unsupported-shorthand" ? ["UNSUPPORTED_SHORTHAND_VALUE", value] : ["INVALID_PROPERTY_VALUE", value];
160
- this.#reportDiagnostic(code, normalizedName, input);
194
+ this.#diagnosticForOutcome(outcome, name, value, priority);
195
+ }
196
+ applyMutations(mutations) {
197
+ const kinds = [];
198
+ const properties = [];
199
+ const values = [];
200
+ const priorities = [];
201
+ for (const mutation of mutations) {
202
+ kinds.push(mutation.kind === "set" ? 0 : 1);
203
+ properties.push(mutation.property);
204
+ values.push(mutation.kind === "set" ? mutation.value : "");
205
+ priorities.push(mutation.kind === "set" ? mutation.priority : "");
206
+ }
207
+ let outcomes;
208
+ try {
209
+ outcomes = this.#state.applyMutations(kinds, properties, values, priorities, this.#reservedNestingDepth());
210
+ } catch (error) {
211
+ rethrowResourceBudgetError(error);
212
+ throw error;
213
+ }
214
+ if (outcomes.length !== mutations.length) throw new Error("SheetOM engine returned an incomplete declaration mutation result.");
215
+ const results = [];
216
+ let mutated = false;
217
+ for (let index = 0; index < outcomes.length; index += 1) {
218
+ const mutation = mutations[index];
219
+ const outcome = outcomes[index];
220
+ if (!mutation || outcome === void 0) throw new Error("SheetOM engine returned a mismatched declaration mutation result.");
221
+ if (mutation.kind === "remove") {
222
+ mutated = true;
223
+ results.push({
224
+ kind: "remove",
225
+ value: outcome
226
+ });
227
+ continue;
228
+ }
229
+ if (outcome === "applied") {
230
+ mutated = true;
231
+ results.push({
232
+ kind: "set",
233
+ accepted: true,
234
+ diagnostic: null
235
+ });
236
+ continue;
237
+ }
238
+ if (!isEngineMutationOutcome(outcome)) throw new Error("SheetOM engine returned an invalid declaration mutation outcome.");
239
+ results.push({
240
+ kind: "set",
241
+ accepted: false,
242
+ diagnostic: this.#diagnosticForOutcome(outcome, mutation.property, mutation.value, mutation.priority)
243
+ });
244
+ }
245
+ if (mutated) this.#invalidateSerialization();
246
+ return results;
161
247
  }
162
248
  removeProperty(name) {
163
249
  const removed = this.#state.removeProperty(name);
@@ -173,19 +259,63 @@ var NativeDeclarationBlock = class {
173
259
  }
174
260
  this.#invalidateSerialization();
175
261
  }
176
- serialize(safe, indent, separator) {
177
- const cacheKey = `${safe ? "1" : "0"}\0${indent}\0${separator}`;
262
+ serialize(safe, indent, separator, strict = false) {
263
+ const cacheKey = `${safe ? "1" : "0"}${strict ? "1" : "0"}\0${indent}\0${separator}`;
178
264
  const cached = this.#serializationCache.get(cacheKey);
179
- if (cached !== void 0) return cached;
180
- const serialized = this.#state.serializeFormatted(safe, indent, separator);
181
- this.#serializationCache.set(cacheKey, serialized);
182
- return serialized;
265
+ if (cached !== void 0) {
266
+ if (typeof cached === "string") return cached;
267
+ this.#reportSerializationIssues(cached.issues);
268
+ return cached.serialized;
269
+ }
270
+ let result;
271
+ try {
272
+ if (strict) result = this.#state.serializeFormatted(safe, indent, separator);
273
+ else result = parseResilientSerializationResult(this.#state.serializeFormattedResilient(safe, indent, separator));
274
+ } catch (error) {
275
+ rethrowResourceBudgetError(error);
276
+ rethrowSerializationError(error);
277
+ throw error;
278
+ }
279
+ this.#serializationCache.set(cacheKey, result);
280
+ if (typeof result === "string") return result;
281
+ this.#reportSerializationIssues(result.issues);
282
+ return result.serialized;
183
283
  }
184
284
  #invalidateSerialization() {
185
285
  this.#observableCache = void 0;
186
286
  this.#serializationCache.clear();
187
287
  }
288
+ #diagnosticForOutcome(outcome, name, value, priority) {
289
+ const normalizedName = name.startsWith("--") ? name : name.toLowerCase();
290
+ const [code, input] = outcome === "invalid-priority" ? ["INVALID_PRIORITY", priority] : outcome === "unsupported-shorthand" ? ["UNSUPPORTED_SHORTHAND_VALUE", value] : ["INVALID_PROPERTY_VALUE", value];
291
+ return this.#reportDiagnostic(code, normalizedName, input);
292
+ }
293
+ #reportSerializationIssues(issues) {
294
+ for (const issue of issues) this.#reportSerializationDiagnostic(issue.shorthand, issue.conflictingLonghands);
295
+ }
188
296
  };
297
+ function parseResilientSerializationResult(output) {
298
+ const serialized = output[0];
299
+ if (serialized === void 0 || output.length % 2 === 0) throw new Error("SheetOM engine returned an invalid resilient serialization result.");
300
+ if (output.length === 1) return serialized;
301
+ const issues = [];
302
+ for (let index = 1; index < output.length; index += 2) {
303
+ const shorthand = output[index];
304
+ const conflicts = output[index + 1];
305
+ if (!shorthand || !conflicts) throw new Error("SheetOM engine returned an incomplete resilient serialization issue.");
306
+ issues.push({
307
+ shorthand,
308
+ conflictingLonghands: Object.freeze(conflicts.split(","))
309
+ });
310
+ }
311
+ return {
312
+ serialized,
313
+ issues
314
+ };
315
+ }
316
+ function isEngineMutationOutcome(value) {
317
+ return value === "invalid-name" || value === "invalid-priority" || value === "invalid-value" || value === "unsupported-shorthand";
318
+ }
189
319
  function toUnsignedLong$1(value) {
190
320
  const numeric = Number(value);
191
321
  if (!Number.isFinite(numeric) || numeric === 0) return 0;
@@ -377,13 +507,20 @@ var Serializer = class {
377
507
  this.#describe = describe;
378
508
  }
379
509
  serialize(node) {
510
+ return this.serializeMany([node]);
511
+ }
512
+ serializeMany(nodes) {
380
513
  const chunks = [];
381
- const pending = [{
382
- kind: "node",
383
- node,
384
- depth: 0,
385
- child: false
386
- }];
514
+ const pending = [];
515
+ for (let index = nodes.length - 1; index >= 0; index -= 1) {
516
+ const node = nodes[index];
517
+ if (node) pending.push({
518
+ kind: "node",
519
+ node,
520
+ depth: 0,
521
+ child: false
522
+ });
523
+ }
387
524
  while (pending.length > 0) {
388
525
  const frame = pending.pop();
389
526
  if (!frame) continue;
@@ -490,6 +627,7 @@ const pageMarginRuleNames = /* @__PURE__ */ new Set([
490
627
  "left-top"
491
628
  ]);
492
629
  const ignoreDiagnostic = () => {};
630
+ const normalizedSelectorText = Symbol("normalizedSelectorText");
493
631
  const ruleDiagnostics = /* @__PURE__ */ new WeakMap();
494
632
  const ruleTree = new RuleTree((rule, parentRule, parentStyleSheet) => {
495
633
  ruleParentage.set(rule, {
@@ -497,7 +635,8 @@ const ruleTree = new RuleTree((rule, parentRule, parentStyleSheet) => {
497
635
  parentStyleSheet
498
636
  });
499
637
  }, (rule) => rule.parentStyleSheet);
500
- const safeSerializer = new Serializer(describeRuleSafe);
638
+ const safeSerializer = new Serializer((rule) => describeRuleSafe(rule, false));
639
+ const strictSerializer = new Serializer((rule) => describeRuleSafe(rule, true));
501
640
  let activeConstructionResourceBudget = defaultResourceBudget;
502
641
  function constructWithResourceBudget(factory, resourceBudget) {
503
642
  const previous = activeConstructionResourceBudget;
@@ -1145,24 +1284,37 @@ var CSSViewTransitionRule = class extends CSSRule {
1145
1284
  var CSSStyleDeclaration = class {
1146
1285
  parentRule;
1147
1286
  #block;
1148
- constructor(parentRule) {
1287
+ constructor(parentRule, initialCssText) {
1149
1288
  assertInternalConstructor("CSSStyleDeclaration");
1150
1289
  this.parentRule = parentRule;
1151
1290
  lockOwnProperties(this, "parentRule");
1152
1291
  const reportDeclarationDiagnostic = (code, property, input) => {
1153
- const priority = code === "INVALID_PRIORITY";
1154
- const unsupportedShorthand = code === "UNSUPPORTED_SHORTHAND_VALUE";
1155
- (ruleDiagnostics.get(this.parentRule) ?? ignoreDiagnostic)({
1292
+ const diagnostic = {
1156
1293
  code,
1157
1294
  severity: "warning",
1158
1295
  operation: "setProperty",
1159
- message: priority ? `The mutation was ignored because ${input} is not a valid priority.` : unsupportedShorthand ? `The value was ignored because the shorthand codec for ${property} cannot expand it.` : `The value was ignored because it is invalid for ${property}.`,
1296
+ message: code === "INVALID_PRIORITY" ? `The mutation was ignored because ${input} is not a valid priority.` : code === "UNSUPPORTED_SHORTHAND_VALUE" ? `The value was ignored because the shorthand codec for ${property} cannot expand it.` : `The value was ignored because it is invalid for ${property}.`,
1160
1297
  property,
1161
1298
  input,
1162
1299
  location: null
1163
- });
1300
+ };
1301
+ (ruleDiagnostics.get(this.parentRule) ?? ignoreDiagnostic)(diagnostic);
1302
+ return diagnostic;
1303
+ };
1304
+ const reportSerializationDiagnostic = (shorthand, conflictingLonghands) => {
1305
+ const diagnostic = {
1306
+ code: "UNREPRESENTABLE_PENDING_SHORTHAND",
1307
+ severity: "warning",
1308
+ operation: "serialize",
1309
+ message: `The pending ${shorthand} shorthand required a best-effort recovery during serialization.`,
1310
+ property: shorthand,
1311
+ input: "",
1312
+ location: null,
1313
+ conflictingLonghands: Object.freeze([...conflictingLonghands])
1314
+ };
1315
+ (ruleDiagnostics.get(this.parentRule) ?? ignoreDiagnostic)(diagnostic);
1164
1316
  };
1165
- this.#block = new NativeDeclarationBlock(reportDeclarationDiagnostic, parentRule instanceof CSSFontFaceRule ? "font-face" : parentRule instanceof CSSFunctionDeclarations ? "function" : "style", ruleResourceBudgets.get(parentRule) ?? defaultResourceBudget, () => declarationSerializationDepth(parentRule));
1317
+ this.#block = new NativeDeclarationBlock(reportDeclarationDiagnostic, reportSerializationDiagnostic, parentRule instanceof CSSFontFaceRule ? "font-face" : parentRule instanceof CSSFunctionDeclarations ? "function" : "style", ruleResourceBudgets.get(parentRule) ?? defaultResourceBudget, () => declarationSerializationDepth(parentRule), initialCssText);
1166
1318
  return new Proxy(this, {
1167
1319
  get(target, property) {
1168
1320
  if (typeof property === "string" && arrayIndexPattern.test(property)) return target.item(Number(property)) || void 0;
@@ -1207,21 +1359,49 @@ var CSSStyleDeclaration = class {
1207
1359
  const stringPriority = priority === null ? "" : `${priority}`;
1208
1360
  this.#block.setProperty(stringName, stringValue, stringPriority);
1209
1361
  }
1362
+ /** Apply ordered declaration mutations through one engine boundary crossing. */
1363
+ applyMutations(mutations) {
1364
+ requireArguments(arguments.length, 1, "CSSStyleDeclaration", "applyMutations");
1365
+ if (!Array.isArray(mutations)) throw new TypeError("CSSStyleDeclaration.applyMutations requires an array.");
1366
+ const normalized = [];
1367
+ for (let index = 0; index < mutations.length; index += 1) {
1368
+ const mutation = mutations[index];
1369
+ if (typeof mutation !== "object" || mutation === null) throw new TypeError(`Declaration mutation at index ${index} must be an object.`);
1370
+ if (mutation.kind !== "set" && mutation.kind !== "remove") throw new TypeError(`Declaration mutation at index ${index} has an invalid kind.`);
1371
+ if (typeof mutation.property !== "string") throw new TypeError(`Declaration mutation at index ${index} requires a string property.`);
1372
+ if (mutation.kind === "remove") {
1373
+ normalized.push({
1374
+ kind: "remove",
1375
+ property: mutation.property
1376
+ });
1377
+ continue;
1378
+ }
1379
+ if (typeof mutation.value !== "string" && mutation.value !== null) throw new TypeError(`Declaration mutation at index ${index} requires a string or null value.`);
1380
+ if (mutation.priority !== void 0 && mutation.priority !== null && typeof mutation.priority !== "string") throw new TypeError(`Declaration mutation at index ${index} requires a string priority.`);
1381
+ normalized.push({
1382
+ kind: "set",
1383
+ property: mutation.property,
1384
+ value: mutation.value ?? "",
1385
+ priority: mutation.priority ?? ""
1386
+ });
1387
+ }
1388
+ return this.#block.applyMutations(normalized);
1389
+ }
1210
1390
  removeProperty(name) {
1211
1391
  requireArguments(arguments.length, 1, "CSSStyleDeclaration", "removeProperty");
1212
1392
  return this.#block.removeProperty(`${name}`);
1213
1393
  }
1214
1394
  /** @internal */
1215
- serializeSafe(indent) {
1216
- return this.#block.serialize(true, indent, "\n");
1395
+ serializeSafe(indent, strict = false) {
1396
+ return this.#block.serialize(true, indent, "\n", strict);
1217
1397
  }
1218
1398
  };
1219
1399
  /** A live `@font-face` descriptor rule. */
1220
1400
  var CSSFontFaceRule = class extends CSSRule {
1221
1401
  style;
1222
- constructor() {
1402
+ constructor(initialCssText) {
1223
1403
  super(CSSRule.FONT_FACE_RULE);
1224
- this.style = constructWebIDL(CSSStyleDeclaration, [this]);
1404
+ this.style = constructWebIDL(CSSStyleDeclaration, [this, initialCssText]);
1225
1405
  lockOwnProperties(this, "style");
1226
1406
  }
1227
1407
  get cssText() {
@@ -1232,9 +1412,9 @@ var CSSFontFaceRule = class extends CSSRule {
1232
1412
  /** Declarations ordered after a nested style rule. */
1233
1413
  var CSSNestedDeclarations = class extends CSSRule {
1234
1414
  #style;
1235
- constructor() {
1415
+ constructor(initialCssText) {
1236
1416
  super(0);
1237
- this.#style = constructWebIDL(CSSStyleDeclaration, [this]);
1417
+ this.#style = constructWebIDL(CSSStyleDeclaration, [this, initialCssText]);
1238
1418
  }
1239
1419
  get style() {
1240
1420
  return this.#style;
@@ -1250,9 +1430,9 @@ var CSSNestedDeclarations = class extends CSSRule {
1250
1430
  /** A run of live descriptors inside a custom function. */
1251
1431
  var CSSFunctionDeclarations = class extends CSSRule {
1252
1432
  #style;
1253
- constructor() {
1433
+ constructor(initialCssText) {
1254
1434
  super(0);
1255
- this.#style = constructWebIDL(CSSFunctionDescriptors, [this]);
1435
+ this.#style = constructWebIDL(CSSFunctionDescriptors, [this, initialCssText]);
1256
1436
  }
1257
1437
  get style() {
1258
1438
  return this.#style;
@@ -1276,10 +1456,10 @@ var CSSFunctionDescriptors = class extends CSSStyleDeclaration {
1276
1456
  var CSSMarginRule = class extends CSSRule {
1277
1457
  name;
1278
1458
  style;
1279
- constructor(name) {
1459
+ constructor(name, initialCssText) {
1280
1460
  super(CSSRule.MARGIN_RULE);
1281
1461
  this.name = name;
1282
- this.style = constructWebIDL(CSSStyleDeclaration, [this]);
1462
+ this.style = constructWebIDL(CSSStyleDeclaration, [this, initialCssText]);
1283
1463
  lockOwnProperties(this, "name", "style");
1284
1464
  }
1285
1465
  get cssText() {
@@ -1291,10 +1471,10 @@ var CSSMarginRule = class extends CSSRule {
1291
1471
  var CSSPageRule = class extends CSSGroupingRule {
1292
1472
  style;
1293
1473
  #selectorText;
1294
- constructor(selectorText) {
1474
+ constructor(selectorText, initialCssText) {
1295
1475
  super(CSSRule.PAGE_RULE);
1296
1476
  this.#selectorText = selectorText;
1297
- this.style = constructWebIDL(CSSStyleDeclaration, [this]);
1477
+ this.style = constructWebIDL(CSSStyleDeclaration, [this, initialCssText]);
1298
1478
  lockOwnProperties(this, "style");
1299
1479
  }
1300
1480
  get selectorText() {
@@ -1315,10 +1495,10 @@ var CSSPageRule = class extends CSSGroupingRule {
1315
1495
  var CSSPositionTryRule = class extends CSSRule {
1316
1496
  name;
1317
1497
  style;
1318
- constructor(name) {
1498
+ constructor(name, initialCssText) {
1319
1499
  super(0);
1320
1500
  this.name = name;
1321
- this.style = constructWebIDL(CSSStyleDeclaration, [this]);
1501
+ this.style = constructWebIDL(CSSStyleDeclaration, [this, initialCssText]);
1322
1502
  lockOwnProperties(this, "name", "style");
1323
1503
  }
1324
1504
  get cssText() {
@@ -1352,10 +1532,10 @@ function normalizeKeyText(value) {
1352
1532
  var CSSKeyframeRule = class extends CSSRule {
1353
1533
  style;
1354
1534
  #keyText;
1355
- constructor(keyText) {
1535
+ constructor(keyText, initialCssText) {
1356
1536
  super(CSSRule.KEYFRAME_RULE);
1357
1537
  this.#keyText = normalizeKeyText(keyText) ?? keyText;
1358
- this.style = constructWebIDL(CSSStyleDeclaration, [this]);
1538
+ this.style = constructWebIDL(CSSStyleDeclaration, [this, initialCssText]);
1359
1539
  lockOwnProperties(this, "style");
1360
1540
  }
1361
1541
  get keyText() {
@@ -1666,10 +1846,10 @@ var CSSFontFeatureValuesRule = class extends CSSRule {
1666
1846
  var CSSStyleRule = class extends CSSGroupingRule {
1667
1847
  style;
1668
1848
  #selectorText;
1669
- constructor(selectorText) {
1849
+ constructor(selectorText, normalized, initialCssText) {
1670
1850
  super(CSSRule.STYLE_RULE);
1671
- this.#selectorText = normalizeSelectorText(`${selectorText}`, currentConstructionResourceBudget()) ?? `${selectorText}`;
1672
- this.style = constructWebIDL(CSSStyleDeclaration, [this]);
1851
+ this.#selectorText = normalized === normalizedSelectorText ? selectorText : normalizeSelectorText(`${selectorText}`, currentConstructionResourceBudget()) ?? `${selectorText}`;
1852
+ this.style = constructWebIDL(CSSStyleDeclaration, [this, initialCssText]);
1673
1853
  lockOwnProperties(this, "style");
1674
1854
  }
1675
1855
  get selectorText() {
@@ -1702,26 +1882,23 @@ function replaceGroupingRules(group, rules) {
1702
1882
  function attachRuleTree(rule, parentRule, parentStyleSheet) {
1703
1883
  ruleTree.attach(rule, parentRule, parentStyleSheet);
1704
1884
  }
1705
- function serializeRuleSafe(rule) {
1706
- return safeSerializer.serialize(rule);
1707
- }
1708
- function describeRuleSafe(rule) {
1885
+ function describeRuleSafe(rule, strict) {
1709
1886
  if (rule instanceof CSSStyleRule) return {
1710
1887
  kind: "block",
1711
1888
  header: rule.selectorText,
1712
- declarations: rule.style.serializeSafe(" "),
1889
+ declarations: rule.style.serializeSafe(" ", strict),
1713
1890
  children: ruleTree.children(rule)
1714
1891
  };
1715
1892
  if (rule instanceof CSSNestedDeclarations || rule instanceof CSSFunctionDeclarations) return {
1716
1893
  kind: "declarations",
1717
- declarations: rule.style.serializeSafe("")
1894
+ declarations: rule.style.serializeSafe("", strict)
1718
1895
  };
1719
1896
  if (rule instanceof CSSFontFaceRule || rule instanceof CSSMarginRule || rule instanceof CSSPositionTryRule || rule instanceof CSSKeyframeRule) {
1720
1897
  const blockIndex = rule.cssText.indexOf("{");
1721
1898
  return {
1722
1899
  kind: "block",
1723
1900
  header: rule.cssText.slice(0, blockIndex).trimEnd(),
1724
- declarations: rule.style.serializeSafe(" ")
1901
+ declarations: rule.style.serializeSafe(" ", strict)
1725
1902
  };
1726
1903
  }
1727
1904
  if (rule instanceof CSSKeyframesRule) return {
@@ -1732,7 +1909,7 @@ function describeRuleSafe(rule) {
1732
1909
  if (rule instanceof CSSPageRule) return {
1733
1910
  kind: "block",
1734
1911
  header: `@page${rule.selectorText === "" ? "" : ` ${rule.selectorText}`}`,
1735
- declarations: rule.style.serializeSafe(" "),
1912
+ declarations: rule.style.serializeSafe(" ", strict),
1736
1913
  children: ruleTree.children(rule)
1737
1914
  };
1738
1915
  if (!(rule instanceof CSSGroupingRule)) return {
@@ -1839,24 +2016,19 @@ function isNativeMetadataRule(description) {
1839
2016
  function createRuleShellFromNative(description, reportDiagnostic, preserveImports, resourceBudget) {
1840
2017
  let rule;
1841
2018
  switch (description.kind) {
1842
- case "style": {
1843
- const style = constructWebIDL(CSSStyleRule, [description.prelude]);
1844
- style.style.cssText = description.declarations;
1845
- rule = style;
2019
+ case "style":
2020
+ rule = constructWebIDL(CSSStyleRule, [
2021
+ description.prelude,
2022
+ normalizedSelectorText,
2023
+ description.declarations
2024
+ ]);
1846
2025
  break;
1847
- }
1848
- case "nested-declarations": {
1849
- const nested = constructWebIDL(CSSNestedDeclarations);
1850
- nested.style.cssText = description.declarations;
1851
- rule = nested;
2026
+ case "nested-declarations":
2027
+ rule = constructWebIDL(CSSNestedDeclarations, [description.declarations]);
1852
2028
  break;
1853
- }
1854
- case "function-declarations": {
1855
- const declarations = constructWebIDL(CSSFunctionDeclarations);
1856
- declarations.style.cssText = description.declarations;
1857
- rule = declarations;
2029
+ case "function-declarations":
2030
+ rule = constructWebIDL(CSSFunctionDeclarations, [description.declarations]);
1858
2031
  break;
1859
- }
1860
2032
  case "import": {
1861
2033
  if (!preserveImports) return null;
1862
2034
  const parsed = parseImportPrelude(description.cssText.replace(/^@import\s+/iu, "").replace(/;\s*$/u, ""));
@@ -1869,39 +2041,24 @@ function createRuleShellFromNative(description, reportDiagnostic, preserveImport
1869
2041
  ]);
1870
2042
  break;
1871
2043
  }
1872
- case "font-face": {
1873
- const fontFace = constructWebIDL(CSSFontFaceRule);
1874
- fontFace.style.cssText = description.declarations;
1875
- rule = fontFace;
2044
+ case "font-face":
2045
+ rule = constructWebIDL(CSSFontFaceRule, [description.declarations]);
1876
2046
  break;
1877
- }
1878
- case "page": {
1879
- const page = constructWebIDL(CSSPageRule, [description.prelude]);
1880
- page.style.cssText = description.declarations;
1881
- rule = page;
2047
+ case "page":
2048
+ rule = constructWebIDL(CSSPageRule, [description.prelude, description.declarations]);
1882
2049
  break;
1883
- }
1884
- case "margin": {
1885
- const margin = constructWebIDL(CSSMarginRule, [description.prelude]);
1886
- margin.style.cssText = description.declarations;
1887
- rule = margin;
2050
+ case "margin":
2051
+ rule = constructWebIDL(CSSMarginRule, [description.prelude, description.declarations]);
1888
2052
  break;
1889
- }
1890
- case "position-try": {
1891
- const positionTry = constructWebIDL(CSSPositionTryRule, [description.prelude]);
1892
- positionTry.style.cssText = description.declarations;
1893
- rule = positionTry;
2053
+ case "position-try":
2054
+ rule = constructWebIDL(CSSPositionTryRule, [description.prelude, description.declarations]);
1894
2055
  break;
1895
- }
1896
2056
  case "keyframes":
1897
2057
  rule = constructWebIDL(CSSKeyframesRule, [description.prelude]);
1898
2058
  break;
1899
- case "keyframe": {
1900
- const keyframe = constructWebIDL(CSSKeyframeRule, [description.prelude]);
1901
- keyframe.style.cssText = description.declarations;
1902
- rule = keyframe;
2059
+ case "keyframe":
2060
+ rule = constructWebIDL(CSSKeyframeRule, [description.prelude, description.declarations]);
1903
2061
  break;
1904
- }
1905
2062
  case "counter-style": {
1906
2063
  const counterStyle = constructWebIDL(CSSCounterStyleRule, [description.prelude]);
1907
2064
  hydrateCounterStyleDescriptors(counterStyle, description.declarations, resourceBudget);
@@ -2127,9 +2284,13 @@ var CSSStyleSheet = class {
2127
2284
  }
2128
2285
  /** Serialize current state as reparsable CSS without mutating live objects. */
2129
2286
  serialize() {
2130
- return this.#rules.map(serializeRuleSafe).join("");
2287
+ return safeSerializer.serializeMany(this.#rules);
2288
+ }
2289
+ /** Serialize current state only when its CSS semantics can be preserved exactly. */
2290
+ serializeStrict() {
2291
+ return strictSerializer.serializeMany(this.#rules);
2131
2292
  }
2132
- /** Drain opt-in mutation diagnostics without affecting CSSOM behavior. */
2293
+ /** Drain opt-in mutation and serialization diagnostics without affecting CSSOM behavior. */
2133
2294
  takeDiagnostics() {
2134
2295
  if (!this.#diagnostics) return [];
2135
2296
  return this.#diagnostics.splice(0);
@@ -2180,6 +2341,7 @@ exports.CSSStyleSheet = CSSStyleSheet;
2180
2341
  exports.CSSSupportsRule = CSSSupportsRule;
2181
2342
  exports.CSSViewTransitionRule = CSSViewTransitionRule;
2182
2343
  exports.MediaList = MediaList;
2344
+ exports.SheetOMSerializationError = SheetOMSerializationError;
2183
2345
  exports.parseStyleSheet = parseStyleSheet;
2184
2346
 
2185
2347
  //# sourceMappingURL=index.cjs.map