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