single-file-core 1.6.7 → 1.6.9

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/core/index.js CHANGED
@@ -435,6 +435,7 @@ const SCRIPT_OPTIONS = "data-single-file-options";
435
435
  const JAVASCRIPT_URI_PROTOCOL = "javascript:";
436
436
  const DISABLED_SCRIPT_URI = "javascript:void(0)";
437
437
  const SCRIPT_URI_ATTRIBUTE_NAMES = ["href", "src", "action", "formaction", "data"];
438
+ const SVG_NAMESPACE_URI = "http://www.w3.org/2000/svg";
438
439
  const UTF8_CHARSET = "utf-8";
439
440
  const TAINTED_CANVAS_WARNING_MESSAGE = "SingleFile: canvas elements tainted by a cross-origin resource, dropped from the page:";
440
441
  const EMPTY_RESOURCE = "data:,";
@@ -1762,13 +1763,15 @@ function isScriptURI(value) {
1762
1763
  }
1763
1764
 
1764
1765
  function getOnEventAttributeNames(doc) {
1765
- const element = doc.body || doc.createElement("div");
1766
+ const elements = [doc.body || doc.createElement("div"), doc.createElementNS(SVG_NAMESPACE_URI, "animate")];
1766
1767
  const attributeNames = [];
1767
- for (const propertyName in element) {
1768
- if (propertyName.startsWith("on")) {
1769
- attributeNames.push(propertyName);
1768
+ elements.forEach(element => {
1769
+ for (const propertyName in element) {
1770
+ if (propertyName.startsWith("on")) {
1771
+ attributeNames.push(propertyName);
1772
+ }
1770
1773
  }
1771
- }
1774
+ });
1772
1775
  return attributeNames;
1773
1776
  }
1774
1777
 
package/core/infobar.js CHANGED
@@ -157,7 +157,7 @@ const INFOBAR_ANIMATIONS_STYLES = `
157
157
  animation-iteration-count: 2;
158
158
  }
159
159
 
160
- .infobar:not(:focus-within):not(.infobar-focus)::after {
160
+ .infobar::after {
161
161
  content: "";
162
162
  position: absolute;
163
163
  inset: -2px;
@@ -170,6 +170,12 @@ const INFOBAR_ANIMATIONS_STYLES = `
170
170
  animation-timing-function: ease-out;
171
171
  animation-delay: 2s;
172
172
  animation-iteration-count: 3;
173
+ transition: visibility 0s 999999s;
174
+ }
175
+
176
+ .infobar:focus-within::after, .infobar.infobar-focus::after {
177
+ visibility: hidden;
178
+ transition-delay: 0s;
173
179
  }
174
180
 
175
181
  @keyframes flash {
@@ -194,7 +200,7 @@ const INFOBAR_ANIMATIONS_STYLES = `
194
200
 
195
201
  @media (prefers-reduced-motion: reduce) {
196
202
  .infobar,
197
- .infobar:not(:focus-within):not(.infobar-focus)::after {
203
+ .infobar::after {
198
204
  animation-name: none;
199
205
  }
200
206
  }
@@ -48,6 +48,7 @@ const IMPORT_NAME = "import";
48
48
  const FONT_FACE_NAME = "font-face";
49
49
  const KEYFRAMES_NAME = "keyframes";
50
50
  const COMBINATOR_NAME = "Combinator";
51
+ const TYPE_SELECTOR_TYPE = "TypeSelector";
51
52
  const STYLE_ATTRIBUTE_NAME = "style";
52
53
  const SELECTOR_LIST_CONTEXT = "selectorList";
53
54
  const STYLESHEET_CONTEXT = "stylesheet";
@@ -58,6 +59,10 @@ const PARSE_CSS_ERROR_MESSAGE = "Failed to parse CSS";
58
59
  const QSA_ERROR_MESSAGE = "Failed to match selector";
59
60
  const PRELUDE_SEPARATOR = ",";
60
61
  const NESTING_SELECTOR = "&";
62
+ const SCOPE_PSEUDO_CLASS = ":scope";
63
+ const NAMESPACE_SEPARATOR = "|";
64
+ const SELECTOR_SUPPORTS_PREFIX = "selector(";
65
+ const SELECTOR_SUPPORTS_SUFFIX = ")";
61
66
  const VENDOR_PREFIX = "-";
62
67
  const CUSTOM_PROPERTY_PREFIX = "--";
63
68
  const LAYER_NAME_SEPARATOR = ".";
@@ -118,6 +123,7 @@ function process(doc, stylesheets) {
118
123
  layerOrder: new Map(),
119
124
  selectorData: new Map(),
120
125
  selectorTexts: new Map(),
126
+ supportedSelectors: new Map(),
121
127
  preludeTexts: new Map(),
122
128
  rulesCounter: 0,
123
129
  scopeIdCounter: 0
@@ -379,6 +385,9 @@ function minifyAtRule(ruleData, cssRule, stylesheets, processingContext, removed
379
385
 
380
386
  function minifyStylesheetRule(ruleData, cssRule, stylesheets, processingContext, removedRules, docContext) {
381
387
  ruleData.order = docContext.rulesCounter++;
388
+ if (!isPreludeSupported(ruleData.prelude, docContext)) {
389
+ return;
390
+ }
382
391
  const removedSelectors = processSelectors(ruleData, processingContext, docContext);
383
392
  const wasDiscarded = removeUnmatchedSelectors(ruleData, removedSelectors, removedRules, cssRule, docContext);
384
393
  if (!wasDiscarded && hasChildNodes(ruleData.block)) {
@@ -388,7 +397,7 @@ function minifyStylesheetRule(ruleData, cssRule, stylesheets, processingContext,
388
397
 
389
398
  function processSelectors(ruleData, processingContext, docContext) {
390
399
  const removedSelectors = [];
391
- const { ancestorsSelectors } = processingContext;
400
+ const { ancestorsSelectors, scopeStack } = processingContext;
392
401
  for (let selector = ruleData.prelude.children.head, selectorIndex = 0; selector; selector = selector.next, selectorIndex++) {
393
402
  const {
394
403
  startsWithCombinator,
@@ -400,7 +409,8 @@ function processSelectors(ruleData, processingContext, docContext) {
400
409
  }
401
410
  registerSelector(selector, ruleData, processingContext, docContext);
402
411
  if (!startsWithCombinator || !ancestorsSelectors || !ancestorsSelectors.length) {
403
- const matchedElements = matchElements(selector, ancestorsSelectors, processingContext.scopeStack, docContext);
412
+ const relativeToScope = startsWithCombinator && Boolean(scopeStack && scopeStack.length);
413
+ const matchedElements = matchElements(selector, ancestorsSelectors, scopeStack, docContext, relativeToScope);
404
414
  if (matchedElements.length) {
405
415
  if (!hasUnqueryableSelector) {
406
416
  updateMatchingSelectors(matchedElements, selector, docContext);
@@ -453,6 +463,45 @@ function analyzeSelector(selector) {
453
463
  return { hasUnqueryableSelector, hasNestedUnqueryablePseudoClass, startsWithCombinator };
454
464
  }
455
465
 
466
+ function isPreludeSupported(prelude, docContext) {
467
+ if (!globalThis.CSS || !globalThis.CSS.supports) {
468
+ return true;
469
+ }
470
+ for (let selector = prelude.children.head; selector; selector = selector.next) {
471
+ if (!isSelectorSupported(selector.data, docContext)) {
472
+ return false;
473
+ }
474
+ }
475
+ return true;
476
+ }
477
+
478
+ function isSelectorSupported(selector, docContext) {
479
+ const selectorNode = cssTree.clone(selector);
480
+ cssTree.walk(selectorNode, {
481
+ visit: TYPE_SELECTOR_TYPE,
482
+ enter(node) {
483
+ if (typeof node.name === "string" && node.name.includes(NAMESPACE_SEPARATOR)) {
484
+ node.name = node.name.substring(node.name.lastIndexOf(NAMESPACE_SEPARATOR) + 1);
485
+ }
486
+ }
487
+ });
488
+ let selectorText = cssTree.generate(selectorNode);
489
+ const firstChild = selectorNode.children && selectorNode.children.head && selectorNode.children.head.data;
490
+ if (firstChild && firstChild.type === COMBINATOR_NAME) {
491
+ selectorText = SCOPE_PSEUDO_CLASS + selectorText;
492
+ }
493
+ if (!docContext.supportedSelectors.has(selectorText)) {
494
+ let supported;
495
+ try {
496
+ supported = globalThis.CSS.supports(SELECTOR_SUPPORTS_PREFIX + selectorText + SELECTOR_SUPPORTS_SUFFIX);
497
+ } catch {
498
+ supported = true;
499
+ }
500
+ docContext.supportedSelectors.set(selectorText, supported);
501
+ }
502
+ return docContext.supportedSelectors.get(selectorText);
503
+ }
504
+
456
505
  function updateMatchingSelectors(matchedElements, selector, docContext) {
457
506
  matchedElements.forEach(element => {
458
507
  docContext.matchedElements.add(element);
@@ -580,8 +629,11 @@ function getConditionalStackForSelector(selector, docContext) {
580
629
  return conditionalStack;
581
630
  }
582
631
 
583
- function matchElements(selector, ancestorsSelectors, scopeStack, docContext) {
584
- const selectorText = createSelectorText(selector, ancestorsSelectors, docContext);
632
+ function matchElements(selector, ancestorsSelectors, scopeStack, docContext, relativeToScope) {
633
+ let selectorText = createSelectorText(selector, ancestorsSelectors, docContext);
634
+ if (relativeToScope) {
635
+ selectorText = SCOPE_PSEUDO_CLASS + selectorText;
636
+ }
585
637
  const cacheKey = createScopeCacheKey(selectorText, scopeStack);
586
638
  const cachedNodes = docContext.matchedSelectors.get(cacheKey);
587
639
  if (cachedNodes) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-core",
3
- "version": "1.6.7",
3
+ "version": "1.6.9",
4
4
  "description": "SingleFile Core",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",