@mulmoclaude/core 4.5.0 → 4.7.0

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.
@@ -138,7 +138,7 @@ IconGlyph.props = {
138
138
  };
139
139
  //#endregion
140
140
  //#region ../../node_modules/dompurify/dist/purify.es.mjs
141
- /*! @license DOMPurify 3.4.13 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.13/LICENSE */
141
+ /*! @license DOMPurify 3.4.14 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.14/LICENSE */
142
142
  function _arrayLikeToArray(r, a) {
143
143
  (null == a || a > r.length) && (a = r.length);
144
144
  for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
@@ -297,9 +297,11 @@ function clone(object) {
297
297
  var _ref3 = _slicedToArray(_ref2, 2);
298
298
  const property = _ref3[0];
299
299
  const value = _ref3[1];
300
- if (objectHasOwnProperty(object, property)) if (arrayIsArray(value)) newObject[property] = cleanArray(value);
301
- else if (value && typeof value === "object" && value.constructor === Object) newObject[property] = clone(value);
302
- else newObject[property] = value;
300
+ if (objectHasOwnProperty(object, property)) {
301
+ if (arrayIsArray(value)) newObject[property] = cleanArray(value);
302
+ else if (value && typeof value === "object" && value.constructor === Object) newObject[property] = clone(value);
303
+ else newObject[property] = value;
304
+ }
303
305
  }
304
306
  return newObject;
305
307
  }
@@ -867,6 +869,7 @@ var svg = freeze([
867
869
  "patterncontentunits",
868
870
  "patterntransform",
869
871
  "patternunits",
872
+ "pointer-events",
870
873
  "points",
871
874
  "preservealpha",
872
875
  "preserveaspectratio",
@@ -921,6 +924,7 @@ var svg = freeze([
921
924
  "u2",
922
925
  "unicode",
923
926
  "values",
927
+ "vector-effect",
924
928
  "viewbox",
925
929
  "visibility",
926
930
  "version",
@@ -1034,6 +1038,24 @@ var NODE_TYPE = {
1034
1038
  documentFragment: 11,
1035
1039
  notation: 12
1036
1040
  };
1041
+ var LITERAL_TEXT_ELEMENT_NAMES = [
1042
+ "style",
1043
+ "script",
1044
+ "xmp",
1045
+ "iframe",
1046
+ "noembed",
1047
+ "noframes",
1048
+ "plaintext",
1049
+ "noscript"
1050
+ ];
1051
+ var LITERAL_TEXT_ELEMENTS = freeze(addToSet({}, LITERAL_TEXT_ELEMENT_NAMES));
1052
+ var LITERAL_TEXT_CLOSE = function() {
1053
+ const map = {};
1054
+ arrayForEach(LITERAL_TEXT_ELEMENT_NAMES, (name) => {
1055
+ map[name] = seal(new RegExp("</" + name + "(?=[\\t\\n\\f\\r />])", "i"));
1056
+ });
1057
+ return freeze(map);
1058
+ }();
1037
1059
  var getGlobal = function getGlobal() {
1038
1060
  return typeof window === "undefined" ? null : window;
1039
1061
  };
@@ -1093,10 +1115,25 @@ var _createHooksMap = function _createHooksMap() {
1093
1115
  var _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
1094
1116
  return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key]) ? addToSet(options.base ? clone(options.base) : {}, cfg[key], options.transform) : fallback;
1095
1117
  };
1118
+ /**
1119
+ * Resolve an object-valued configuration option: a prototype-free clone
1120
+ * of cfg[key] when it is an own, truthy object property, else a fresh
1121
+ * fallback built by makeFallback (fresh on every parse, so a previous
1122
+ * parse can never leak state into the next one).
1123
+ *
1124
+ * @param cfg the cloned, prototype-free configuration object
1125
+ * @param key the configuration property to read
1126
+ * @param makeFallback builds the fallback value when the option is absent
1127
+ * @returns the resolved object
1128
+ */
1129
+ var _resolveObjectOption = function _resolveObjectOption(cfg, key, makeFallback) {
1130
+ const value = objectHasOwnProperty(cfg, key) ? cfg[key] : void 0;
1131
+ return value && typeof value === "object" ? clone(value) : makeFallback();
1132
+ };
1096
1133
  function createDOMPurify() {
1097
1134
  let window = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : getGlobal();
1098
1135
  const DOMPurify = (root) => createDOMPurify(root);
1099
- DOMPurify.version = "3.4.13";
1136
+ DOMPurify.version = "3.4.14";
1100
1137
  DOMPurify.removed = [];
1101
1138
  if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
1102
1139
  DOMPurify.isSupported = false;
@@ -1121,6 +1158,12 @@ function createDOMPurify() {
1121
1158
  const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, "nodeType") : null;
1122
1159
  const getNodeName = Node && Node.prototype ? lookupGetter(Node.prototype, "nodeName") : null;
1123
1160
  const getOwnerDocument = Node && Node.prototype ? lookupGetter(Node.prototype, "ownerDocument") : null;
1161
+ const _readNodeType = function _readNodeType(node) {
1162
+ return getNodeType ? getNodeType(node) : node.nodeType;
1163
+ };
1164
+ const _readNodeName = function _readNodeName(node) {
1165
+ return getNodeName ? getNodeName(node) : node.nodeName;
1166
+ };
1124
1167
  if (typeof HTMLTemplateElement === "function") {
1125
1168
  const template = document.createElement("template");
1126
1169
  if (template.content && template.content.ownerDocument) document = template.content.ownerDocument;
@@ -1378,9 +1421,9 @@ function createDOMPurify() {
1378
1421
  IN_PLACE = cfg.IN_PLACE || false;
1379
1422
  IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI;
1380
1423
  NAMESPACE = typeof cfg.NAMESPACE === "string" ? cfg.NAMESPACE : HTML_NAMESPACE;
1381
- MATHML_TEXT_INTEGRATION_POINTS = objectHasOwnProperty(cfg, "MATHML_TEXT_INTEGRATION_POINTS") && cfg.MATHML_TEXT_INTEGRATION_POINTS && typeof cfg.MATHML_TEXT_INTEGRATION_POINTS === "object" ? clone(cfg.MATHML_TEXT_INTEGRATION_POINTS) : addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS);
1382
- HTML_INTEGRATION_POINTS = objectHasOwnProperty(cfg, "HTML_INTEGRATION_POINTS") && cfg.HTML_INTEGRATION_POINTS && typeof cfg.HTML_INTEGRATION_POINTS === "object" ? clone(cfg.HTML_INTEGRATION_POINTS) : addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS);
1383
- const customElementHandling = objectHasOwnProperty(cfg, "CUSTOM_ELEMENT_HANDLING") && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === "object" ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create(null);
1424
+ MATHML_TEXT_INTEGRATION_POINTS = _resolveObjectOption(cfg, "MATHML_TEXT_INTEGRATION_POINTS", () => addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS));
1425
+ HTML_INTEGRATION_POINTS = _resolveObjectOption(cfg, "HTML_INTEGRATION_POINTS", () => addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS));
1426
+ const customElementHandling = _resolveObjectOption(cfg, "CUSTOM_ELEMENT_HANDLING", () => create(null));
1384
1427
  CUSTOM_ELEMENT_HANDLING = create(null);
1385
1428
  if (objectHasOwnProperty(customElementHandling, "tagNameCheck") && isRegexOrFunction(customElementHandling.tagNameCheck)) CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck;
1386
1429
  if (objectHasOwnProperty(customElementHandling, "attributeNameCheck") && isRegexOrFunction(customElementHandling.attributeNameCheck)) CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck;
@@ -1427,11 +1470,6 @@ function createDOMPurify() {
1427
1470
  addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
1428
1471
  }
1429
1472
  }
1430
- if (objectHasOwnProperty(cfg, "ADD_URI_SAFE_ATTR") && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
1431
- if (objectHasOwnProperty(cfg, "FORBID_CONTENTS") && arrayIsArray(cfg.FORBID_CONTENTS)) {
1432
- if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) FORBID_CONTENTS = clone(FORBID_CONTENTS);
1433
- addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
1434
- }
1435
1473
  if (objectHasOwnProperty(cfg, "ADD_FORBID_CONTENTS") && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {
1436
1474
  if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) FORBID_CONTENTS = clone(FORBID_CONTENTS);
1437
1475
  addToSet(FORBID_CONTENTS, cfg.ADD_FORBID_CONTENTS, transformCaseFunc);
@@ -1548,6 +1586,32 @@ function createDOMPurify() {
1548
1586
  }
1549
1587
  };
1550
1588
  /**
1589
+ * _stripAttributeNode
1590
+ *
1591
+ * Remove a single Attr node case/namespace-exactly on an attribute-teardown
1592
+ * path. Name-based removeAttribute() ASCII-lowercases its lookup key for an
1593
+ * HTML element in an HTML document and so silently misses a case-preserved
1594
+ * handler (e.g. `ONERROR` off an XML/XHTML import) - the same defect
1595
+ * _removeAttribute() was fixed for, which a name-based call would reintroduce
1596
+ * on these IN_PLACE teardown paths. Unlike _removeAttribute this does not
1597
+ * record into DOMPurify.removed: the neutralize passes intentionally do not
1598
+ * book-keep. A clobbered/detached node falls back to best-effort name-based
1599
+ * removal.
1600
+ *
1601
+ * @param element the element to strip the attribute from
1602
+ * @param attribute the Attr node to remove
1603
+ * @param name the attribute's name, for the fallback path
1604
+ */
1605
+ const _stripAttributeNode = function _stripAttributeNode(element, attribute, name) {
1606
+ try {
1607
+ element.removeAttributeNode(attribute);
1608
+ } catch (_) {
1609
+ try {
1610
+ element.removeAttribute(name);
1611
+ } catch (_) {}
1612
+ }
1613
+ };
1614
+ /**
1551
1615
  * _neutralizeRoot
1552
1616
  *
1553
1617
  * Fail-closed teardown of an in-place root after the sanitize walk aborts
@@ -1581,36 +1645,53 @@ function createDOMPurify() {
1581
1645
  if (attributes) for (let i = attributes.length - 1; i >= 0; --i) {
1582
1646
  const attribute = attributes[i];
1583
1647
  const name = attribute && attribute.name;
1584
- if (typeof name === "string") try {
1585
- root.removeAttribute(name);
1586
- } catch (_) {}
1648
+ if (typeof name === "string") _stripAttributeNode(root, attribute, name);
1587
1649
  }
1588
1650
  };
1589
1651
  /**
1590
1652
  * _removeAttribute
1591
1653
  *
1654
+ * Name-based getAttributeNode()/removeAttribute() ASCII-lowercase their
1655
+ * lookup key for HTML elements in an HTML document, so they silently miss an
1656
+ * attribute whose stored qualified name still contains uppercase ASCII
1657
+ * letters. That happens when the node came from a case-preserving source
1658
+ * (an XML/XHTML document imported via importNode(), or createAttributeNS()),
1659
+ * where e.g. `ONERROR` survives the walk: the policy check lowercases to
1660
+ * `onerror` and rejects it, but `removeAttribute('ONERROR')` looks up
1661
+ * `onerror` and finds nothing. Remove the exact Attr node instead, which is
1662
+ * case- and namespace-exact, and fall back to name-based removal only when
1663
+ * the caller could not supply the node.
1664
+ *
1592
1665
  * @param name an Attribute name
1593
1666
  * @param element a DOM node
1667
+ * @param attr the exact Attr node to remove, when the caller has it
1594
1668
  */
1595
- const _removeAttribute = function _removeAttribute(name, element) {
1669
+ const _removeAttribute = function _removeAttribute(name, element, attr) {
1670
+ if (!attr) try {
1671
+ attr = element.getAttributeNode(name);
1672
+ } catch (_) {
1673
+ attr = null;
1674
+ }
1675
+ arrayPush(DOMPurify.removed, {
1676
+ attribute: attr || null,
1677
+ from: element
1678
+ });
1596
1679
  try {
1597
- arrayPush(DOMPurify.removed, {
1598
- attribute: element.getAttributeNode(name),
1599
- from: element
1600
- });
1680
+ if (attr) element.removeAttributeNode(attr);
1681
+ else element.removeAttribute(name);
1601
1682
  } catch (_) {
1602
- arrayPush(DOMPurify.removed, {
1603
- attribute: null,
1604
- from: element
1605
- });
1683
+ try {
1684
+ element.removeAttribute(name);
1685
+ } catch (_) {}
1686
+ }
1687
+ if (name === "is") {
1688
+ if (RETURN_DOM || RETURN_DOM_FRAGMENT) try {
1689
+ _forceRemove(element);
1690
+ } catch (_) {}
1691
+ else try {
1692
+ element.setAttribute(name, "");
1693
+ } catch (_) {}
1606
1694
  }
1607
- element.removeAttribute(name);
1608
- if (name === "is") if (RETURN_DOM || RETURN_DOM_FRAGMENT) try {
1609
- _forceRemove(element);
1610
- } catch (_) {}
1611
- else try {
1612
- element.setAttribute(name, "");
1613
- } catch (_) {}
1614
1695
  };
1615
1696
  /**
1616
1697
  * _stripDisallowedAttributes
@@ -1629,9 +1710,7 @@ function createDOMPurify() {
1629
1710
  const attribute = attributes[i];
1630
1711
  const name = attribute && attribute.name;
1631
1712
  if (typeof name !== "string" || ALLOWED_ATTR[transformCaseFunc(name)]) continue;
1632
- try {
1633
- element.removeAttribute(name);
1634
- } catch (_) {}
1713
+ _stripAttributeNode(element, attribute, name);
1635
1714
  }
1636
1715
  };
1637
1716
  /**
@@ -1660,7 +1739,7 @@ function createDOMPurify() {
1660
1739
  const stack = [root];
1661
1740
  while (stack.length > 0) {
1662
1741
  const node = stack.pop();
1663
- if ((getNodeType ? getNodeType(node) : node.nodeType) === NODE_TYPE.element) _stripDisallowedAttributes(node);
1742
+ if (_readNodeType(node) === NODE_TYPE.element) _stripDisallowedAttributes(node);
1664
1743
  const childNodes = getChildNodes(node);
1665
1744
  if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push(childNodes[i]);
1666
1745
  }
@@ -1700,12 +1779,28 @@ function createDOMPurify() {
1700
1779
  *
1701
1780
  * @param root the in-place root to sweep
1702
1781
  */
1782
+ /**
1783
+ * Central policy for declarative-partial-updates patch-linkage attributes,
1784
+ * shared by the _neutralizePatchLinkage pre-pass and _isValidAttribute so
1785
+ * the two sites cannot drift: `patchsrc` always links, `for` links
1786
+ * everywhere except on <label>/<output>, and the whole policy is gated on
1787
+ * SAFE_FOR_XML (see the rationale block in _isValidAttribute).
1788
+ *
1789
+ * @param lcName the transformCaseFunc'd attribute name
1790
+ * @param lcTag the transformCaseFunc'd tag name of the carrying element
1791
+ * @return true if the attribute is patch linkage and must be dropped
1792
+ */
1793
+ const _isPatchLinkageAttribute = function _isPatchLinkageAttribute(lcName, lcTag) {
1794
+ if (!SAFE_FOR_XML) return false;
1795
+ if (lcName === "patchsrc") return true;
1796
+ return lcName === "for" && lcTag !== "label" && lcTag !== "output";
1797
+ };
1703
1798
  const _neutralizePatchLinkage = function _neutralizePatchLinkage(root) {
1704
1799
  if (!SAFE_FOR_XML) return;
1705
1800
  const stack = [root];
1706
1801
  while (stack.length > 0) {
1707
1802
  const node = stack.pop();
1708
- const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
1803
+ const nodeType = _readNodeType(node);
1709
1804
  if (nodeType === NODE_TYPE.processingInstruction || nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, node.data)) {
1710
1805
  try {
1711
1806
  remove(node);
@@ -1714,10 +1809,10 @@ function createDOMPurify() {
1714
1809
  }
1715
1810
  if (nodeType === NODE_TYPE.element) {
1716
1811
  const element = node;
1717
- const lcTag = transformCaseFunc(getNodeName ? getNodeName(node) : node.nodeName);
1812
+ const lcTag = transformCaseFunc(_readNodeName(node));
1718
1813
  try {
1719
1814
  if (element.hasAttribute && element.hasAttribute("patchsrc")) element.removeAttribute("patchsrc");
1720
- if (element.hasAttribute && element.hasAttribute("for") && lcTag !== "label" && lcTag !== "output") element.removeAttribute("for");
1815
+ if (element.hasAttribute && element.hasAttribute("for") && _isPatchLinkageAttribute("for", lcTag)) element.removeAttribute("for");
1721
1816
  } catch (_) {}
1722
1817
  }
1723
1818
  const childNodes = getChildNodes(node);
@@ -1886,12 +1981,32 @@ function createDOMPurify() {
1886
1981
  */
1887
1982
  const _isUnsafeNode = function _isUnsafeNode(currentNode, tagName) {
1888
1983
  if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.innerHTML)) return true;
1889
- if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === "style" && _isNode(currentNode.firstElementChild)) return true;
1984
+ if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && LITERAL_TEXT_ELEMENTS[tagName] && (_isNode(currentNode.firstElementChild) || typeof currentNode.textContent === "string" && regExpTest(LITERAL_TEXT_CLOSE[tagName], currentNode.textContent))) return true;
1890
1985
  if (currentNode.nodeType === NODE_TYPE.processingInstruction) return true;
1891
1986
  if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, currentNode.data)) return true;
1892
1987
  return false;
1893
1988
  };
1894
1989
  /**
1990
+ * Evaluate a CUSTOM_ELEMENT_HANDLING check (a RegExp or a predicate
1991
+ * function, per the validation in _parseConfig) against a name.
1992
+ * Additional arguments are forwarded to predicate functions - the
1993
+ * attributeNameCheck predicate receives the tag name as its second
1994
+ * argument. A null/absent check never matches.
1995
+ *
1996
+ * @param check the configured tagNameCheck / attributeNameCheck value
1997
+ * @param name the name to test
1998
+ * @param args extra arguments forwarded to a predicate function
1999
+ * @return true if the check matches the name
2000
+ */
2001
+ const _matchesNameCheck = function _matchesNameCheck(check, name) {
2002
+ if (check instanceof RegExp) return regExpTest(check, name);
2003
+ if (check instanceof Function) {
2004
+ for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) args[_key - 2] = arguments[_key];
2005
+ return Boolean(check(name, ...args));
2006
+ }
2007
+ return false;
2008
+ };
2009
+ /**
1895
2010
  * Handle a node whose tag is forbidden or not allowlisted: keep
1896
2011
  * allowed custom elements (false return exits _sanitizeElements
1897
2012
  * early - the namespace and fallback-tag removal checks are
@@ -1909,10 +2024,7 @@ function createDOMPurify() {
1909
2024
  * @return true if the node was removed, false if kept
1910
2025
  */
1911
2026
  const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName, root) {
1912
- if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
1913
- if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
1914
- if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) return false;
1915
- }
2027
+ if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName) && _matchesNameCheck(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
1916
2028
  if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
1917
2029
  const parentNode = getParentNode(currentNode);
1918
2030
  const childNodes = getChildNodes(currentNode);
@@ -1946,6 +2058,42 @@ function createDOMPurify() {
1946
2058
  return set === defaultSet || set === setConfigSet ? clone(set) : set;
1947
2059
  };
1948
2060
  /**
2061
+ * Shared guard for a node that a hook has detached from the walk tree,
2062
+ * used after each element-hook site in _sanitizeElements. Detaching is a
2063
+ * long-standing user pattern (issue #469; draw.io-style foreignObject
2064
+ * filtering). Per the cached, unclobberable parentNode getter the node is
2065
+ * genuinely out of the tree, so it can reach neither the serialized
2066
+ * output nor an IN_PLACE live tree; treat it as removed and stop
2067
+ * processing it. Without this guard, the unsafe-node / namespace checks
2068
+ * would call _forceRemove on a parentless node and hit the REPORT-3
2069
+ * fail-closed throw — which exists for nodes DOMPurify wants gone but
2070
+ * *cannot* detach (clobbered / parentless roots), the opposite of a node
2071
+ * that is already safely gone. The walk root is exempt: a detached
2072
+ * IN_PLACE root is legitimate input and must still be fully sanitized,
2073
+ * and a kill-decision on it must keep hitting the REPORT-3 throw.
2074
+ *
2075
+ * Nodes detached by hooks stay the hook's responsibility for placement:
2076
+ * they are not recorded in DOMPurify.removed, so the post-walk IN_PLACE
2077
+ * pass (which iterates DOMPurify.removed) does not reach them. But a
2078
+ * hook-detached subtree can still hold a queued resource-event handler -
2079
+ * e.g. an <img onload> that began loading when the caller built the live
2080
+ * tree - which fires in page scope after sanitize returns even though the
2081
+ * handler never reached the returned tree. That is the audit-5 F1 hazard,
2082
+ * and the documented node.remove() hook pattern walks straight into it.
2083
+ * So on the IN_PLACE path we neutralize the detached subtree inline,
2084
+ * stripping its non-allow-listed attributes before returning, exactly as
2085
+ * the post-walk pass does for _forceRemove'd subtrees.
2086
+ *
2087
+ * @param currentNode the node a hook may have detached
2088
+ * @param root the current walk root
2089
+ * @return true if the node is detached and now handled, false otherwise
2090
+ */
2091
+ const _handleHookDetachedNode = function _handleHookDetachedNode(currentNode, root) {
2092
+ if (currentNode === root || getParentNode(currentNode) !== null) return false;
2093
+ if (IN_PLACE) _neutralizeSubtree(currentNode);
2094
+ return true;
2095
+ };
2096
+ /**
1949
2097
  * _sanitizeElements
1950
2098
  *
1951
2099
  * @protect nodeName
@@ -1956,24 +2104,18 @@ function createDOMPurify() {
1956
2104
  */
1957
2105
  const _sanitizeElements = function _sanitizeElements(currentNode, root) {
1958
2106
  _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
1959
- if (currentNode !== root && getParentNode(currentNode) === null) {
1960
- if (IN_PLACE) _neutralizeSubtree(currentNode);
1961
- return true;
1962
- }
2107
+ if (_handleHookDetachedNode(currentNode, root)) return true;
1963
2108
  if (_isClobbered(currentNode)) {
1964
2109
  _forceRemove(currentNode);
1965
2110
  return true;
1966
2111
  }
1967
- const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
2112
+ const tagName = transformCaseFunc(_readNodeName(currentNode));
1968
2113
  ALLOWED_TAGS = _forkSharedAllowlist(hooks.uponSanitizeElement, ALLOWED_TAGS, DEFAULT_ALLOWED_TAGS, SET_CONFIG_ALLOWED_TAGS);
1969
2114
  _executeHooks(hooks.uponSanitizeElement, currentNode, {
1970
2115
  tagName,
1971
2116
  allowedTags: ALLOWED_TAGS
1972
2117
  });
1973
- if (currentNode !== root && getParentNode(currentNode) === null) {
1974
- if (IN_PLACE) _neutralizeSubtree(currentNode);
1975
- return true;
1976
- }
2118
+ if (_handleHookDetachedNode(currentNode, root)) return true;
1977
2119
  if (_isUnsafeNode(currentNode, tagName)) {
1978
2120
  _forceRemove(currentNode);
1979
2121
  return true;
@@ -1983,7 +2125,7 @@ function createDOMPurify() {
1983
2125
  if (removed === false) _executeHooks(hooks.afterSanitizeElements, currentNode, null);
1984
2126
  return removed;
1985
2127
  }
1986
- if ((getNodeType ? getNodeType(currentNode) : currentNode.nodeType) === NODE_TYPE.element && !_checkValidNamespace(currentNode)) {
2128
+ if (_readNodeType(currentNode) === NODE_TYPE.element && !_checkValidNamespace(currentNode)) {
1987
2129
  _forceRemove(currentNode);
1988
2130
  return true;
1989
2131
  }
@@ -2011,20 +2153,17 @@ function createDOMPurify() {
2011
2153
  */
2012
2154
  const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
2013
2155
  if (FORBID_ATTR[lcName]) return false;
2014
- if (SAFE_FOR_XML && lcName === "patchsrc") return false;
2015
- if (SAFE_FOR_XML && lcName === "for" && lcTag !== "label" && lcTag !== "output") return false;
2156
+ if (_isPatchLinkageAttribute(lcName, lcTag)) return false;
2016
2157
  if (SANITIZE_DOM && (lcName === "id" || lcName === "name") && (value in document || value in formElement)) return false;
2017
2158
  const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
2018
- if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName));
2019
- else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName));
2020
- else if (!nameIsPermitted) if (_isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName, lcTag)) || lcName === "is" && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value)));
2021
- else return false;
2022
- else if (URI_SAFE_ATTRIBUTES[lcName]);
2023
- else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, "")));
2024
- else if ((lcName === "src" || lcName === "xlink:href" || lcName === "href") && lcTag !== "script" && stringIndexOf(value, "data:") === 0 && DATA_URI_TAGS[lcTag]);
2025
- else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, "")));
2026
- else if (value) return false;
2027
- return true;
2159
+ if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName)) return true;
2160
+ if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) return true;
2161
+ if (!nameIsPermitted) return _isBasicCustomElement(lcTag) && _matchesNameCheck(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) && _matchesNameCheck(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName, lcTag) || lcName === "is" && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && _matchesNameCheck(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value);
2162
+ if (URI_SAFE_ATTRIBUTES[lcName]) return true;
2163
+ if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, ""))) return true;
2164
+ if ((lcName === "src" || lcName === "xlink:href" || lcName === "href") && lcTag !== "script" && stringIndexOf(value, "data:") === 0 && DATA_URI_TAGS[lcTag]) return true;
2165
+ if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, ""))) return true;
2166
+ return !value;
2028
2167
  };
2029
2168
  const RESERVED_CUSTOM_ELEMENT_NAMES = addToSet({}, [
2030
2169
  "annotation-xml",
@@ -2126,29 +2265,29 @@ function createDOMPurify() {
2126
2265
  _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
2127
2266
  value = hookEvent.attrValue;
2128
2267
  if (SANITIZE_NAMED_PROPS && (lcName === "id" || lcName === "name") && stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0) {
2129
- _removeAttribute(name, currentNode);
2268
+ _removeAttribute(name, currentNode, attr);
2130
2269
  value = SANITIZE_NAMED_PROPS_PREFIX + value;
2131
2270
  }
2132
2271
  if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
2133
- _removeAttribute(name, currentNode);
2272
+ _removeAttribute(name, currentNode, attr);
2134
2273
  continue;
2135
2274
  }
2136
2275
  if (lcName === "attributename" && stringMatch(value, "href")) {
2137
- _removeAttribute(name, currentNode);
2276
+ _removeAttribute(name, currentNode, attr);
2138
2277
  continue;
2139
2278
  }
2140
2279
  if (hookEvent.forceKeepAttr) continue;
2141
2280
  if (!hookEvent.keepAttr) {
2142
- _removeAttribute(name, currentNode);
2281
+ _removeAttribute(name, currentNode, attr);
2143
2282
  continue;
2144
2283
  }
2145
2284
  if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value)) {
2146
- _removeAttribute(name, currentNode);
2285
+ _removeAttribute(name, currentNode, attr);
2147
2286
  continue;
2148
2287
  }
2149
2288
  if (SAFE_FOR_TEMPLATES) value = _stripTemplateExpressions(value);
2150
2289
  if (!_isValidAttribute(lcTag, lcName, value)) {
2151
- _removeAttribute(name, currentNode);
2290
+ _removeAttribute(name, currentNode, attr);
2152
2291
  continue;
2153
2292
  }
2154
2293
  value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
@@ -2170,7 +2309,7 @@ function createDOMPurify() {
2170
2309
  _sanitizeElements(shadowNode, fragment);
2171
2310
  _sanitizeAttributes(shadowNode);
2172
2311
  if (_isDocumentFragment(shadowNode.content)) _sanitizeShadowDOM2(shadowNode.content);
2173
- if ((getNodeType ? getNodeType(shadowNode) : shadowNode.nodeType) === NODE_TYPE.element) {
2312
+ if (_readNodeType(shadowNode) === NODE_TYPE.element) {
2174
2313
  const innerSr = getShadowRoot(shadowNode);
2175
2314
  if (_isDocumentFragment(innerSr)) {
2176
2315
  _sanitizeAttachedShadowRoots(innerSr);
@@ -2211,7 +2350,7 @@ function createDOMPurify() {
2211
2350
  continue;
2212
2351
  }
2213
2352
  const node = item.node;
2214
- const isElement = (getNodeType ? getNodeType(node) : node.nodeType) === NODE_TYPE.element;
2353
+ const isElement = _readNodeType(node) === NODE_TYPE.element;
2215
2354
  const childNodes = getChildNodes(node);
2216
2355
  if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push({
2217
2356
  node: childNodes[i],
@@ -2262,7 +2401,7 @@ function createDOMPurify() {
2262
2401
  const inPlace = IN_PLACE && typeof dirty !== "string" && _isNode(dirty);
2263
2402
  if (inPlace) {
2264
2403
  _neutralizePatchLinkage(dirty);
2265
- const nn = getNodeName ? getNodeName(dirty) : dirty.nodeName;
2404
+ const nn = _readNodeName(dirty);
2266
2405
  if (typeof nn === "string") {
2267
2406
  const tagName = transformCaseFunc(nn);
2268
2407
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {