@mulmoclaude/core 4.4.0 → 4.4.2

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.
@@ -87,7 +87,7 @@ function useClipboardCopy(resetMs = DEFAULT_RESET_MS) {
87
87
  }
88
88
  //#endregion
89
89
  //#region ../../node_modules/dompurify/dist/purify.es.mjs
90
- /*! @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 */
90
+ /*! @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 */
91
91
  function _arrayLikeToArray(r, a) {
92
92
  (null == a || a > r.length) && (a = r.length);
93
93
  for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
@@ -816,6 +816,7 @@ var svg = freeze([
816
816
  "patterncontentunits",
817
817
  "patterntransform",
818
818
  "patternunits",
819
+ "pointer-events",
819
820
  "points",
820
821
  "preservealpha",
821
822
  "preserveaspectratio",
@@ -870,6 +871,7 @@ var svg = freeze([
870
871
  "u2",
871
872
  "unicode",
872
873
  "values",
874
+ "vector-effect",
873
875
  "viewbox",
874
876
  "visibility",
875
877
  "version",
@@ -983,6 +985,24 @@ var NODE_TYPE = {
983
985
  documentFragment: 11,
984
986
  notation: 12
985
987
  };
988
+ var LITERAL_TEXT_ELEMENT_NAMES = [
989
+ "style",
990
+ "script",
991
+ "xmp",
992
+ "iframe",
993
+ "noembed",
994
+ "noframes",
995
+ "plaintext",
996
+ "noscript"
997
+ ];
998
+ var LITERAL_TEXT_ELEMENTS = freeze(addToSet({}, LITERAL_TEXT_ELEMENT_NAMES));
999
+ var LITERAL_TEXT_CLOSE = function() {
1000
+ const map = {};
1001
+ arrayForEach(LITERAL_TEXT_ELEMENT_NAMES, (name) => {
1002
+ map[name] = seal(new RegExp("</" + name + "(?=[\\t\\n\\f\\r />])", "i"));
1003
+ });
1004
+ return freeze(map);
1005
+ }();
986
1006
  var getGlobal = function getGlobal() {
987
1007
  return typeof window === "undefined" ? null : window;
988
1008
  };
@@ -1042,10 +1062,25 @@ var _createHooksMap = function _createHooksMap() {
1042
1062
  var _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
1043
1063
  return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key]) ? addToSet(options.base ? clone(options.base) : {}, cfg[key], options.transform) : fallback;
1044
1064
  };
1065
+ /**
1066
+ * Resolve an object-valued configuration option: a prototype-free clone
1067
+ * of cfg[key] when it is an own, truthy object property, else a fresh
1068
+ * fallback built by makeFallback (fresh on every parse, so a previous
1069
+ * parse can never leak state into the next one).
1070
+ *
1071
+ * @param cfg the cloned, prototype-free configuration object
1072
+ * @param key the configuration property to read
1073
+ * @param makeFallback builds the fallback value when the option is absent
1074
+ * @returns the resolved object
1075
+ */
1076
+ var _resolveObjectOption = function _resolveObjectOption(cfg, key, makeFallback) {
1077
+ const value = objectHasOwnProperty(cfg, key) ? cfg[key] : void 0;
1078
+ return value && typeof value === "object" ? clone(value) : makeFallback();
1079
+ };
1045
1080
  function createDOMPurify() {
1046
1081
  let window = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : getGlobal();
1047
1082
  const DOMPurify = (root) => createDOMPurify(root);
1048
- DOMPurify.version = "3.4.13";
1083
+ DOMPurify.version = "3.4.14";
1049
1084
  DOMPurify.removed = [];
1050
1085
  if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
1051
1086
  DOMPurify.isSupported = false;
@@ -1070,6 +1105,12 @@ function createDOMPurify() {
1070
1105
  const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, "nodeType") : null;
1071
1106
  const getNodeName = Node && Node.prototype ? lookupGetter(Node.prototype, "nodeName") : null;
1072
1107
  const getOwnerDocument = Node && Node.prototype ? lookupGetter(Node.prototype, "ownerDocument") : null;
1108
+ const _readNodeType = function _readNodeType(node) {
1109
+ return getNodeType ? getNodeType(node) : node.nodeType;
1110
+ };
1111
+ const _readNodeName = function _readNodeName(node) {
1112
+ return getNodeName ? getNodeName(node) : node.nodeName;
1113
+ };
1073
1114
  if (typeof HTMLTemplateElement === "function") {
1074
1115
  const template = document.createElement("template");
1075
1116
  if (template.content && template.content.ownerDocument) document = template.content.ownerDocument;
@@ -1327,9 +1368,9 @@ function createDOMPurify() {
1327
1368
  IN_PLACE = cfg.IN_PLACE || false;
1328
1369
  IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI;
1329
1370
  NAMESPACE = typeof cfg.NAMESPACE === "string" ? cfg.NAMESPACE : HTML_NAMESPACE;
1330
- 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);
1331
- 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);
1332
- const customElementHandling = objectHasOwnProperty(cfg, "CUSTOM_ELEMENT_HANDLING") && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === "object" ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create(null);
1371
+ MATHML_TEXT_INTEGRATION_POINTS = _resolveObjectOption(cfg, "MATHML_TEXT_INTEGRATION_POINTS", () => addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS));
1372
+ HTML_INTEGRATION_POINTS = _resolveObjectOption(cfg, "HTML_INTEGRATION_POINTS", () => addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS));
1373
+ const customElementHandling = _resolveObjectOption(cfg, "CUSTOM_ELEMENT_HANDLING", () => create(null));
1333
1374
  CUSTOM_ELEMENT_HANDLING = create(null);
1334
1375
  if (objectHasOwnProperty(customElementHandling, "tagNameCheck") && isRegexOrFunction(customElementHandling.tagNameCheck)) CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck;
1335
1376
  if (objectHasOwnProperty(customElementHandling, "attributeNameCheck") && isRegexOrFunction(customElementHandling.attributeNameCheck)) CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck;
@@ -1376,11 +1417,6 @@ function createDOMPurify() {
1376
1417
  addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
1377
1418
  }
1378
1419
  }
1379
- if (objectHasOwnProperty(cfg, "ADD_URI_SAFE_ATTR") && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
1380
- if (objectHasOwnProperty(cfg, "FORBID_CONTENTS") && arrayIsArray(cfg.FORBID_CONTENTS)) {
1381
- if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) FORBID_CONTENTS = clone(FORBID_CONTENTS);
1382
- addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
1383
- }
1384
1420
  if (objectHasOwnProperty(cfg, "ADD_FORBID_CONTENTS") && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {
1385
1421
  if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) FORBID_CONTENTS = clone(FORBID_CONTENTS);
1386
1422
  addToSet(FORBID_CONTENTS, cfg.ADD_FORBID_CONTENTS, transformCaseFunc);
@@ -1497,6 +1533,32 @@ function createDOMPurify() {
1497
1533
  }
1498
1534
  };
1499
1535
  /**
1536
+ * _stripAttributeNode
1537
+ *
1538
+ * Remove a single Attr node case/namespace-exactly on an attribute-teardown
1539
+ * path. Name-based removeAttribute() ASCII-lowercases its lookup key for an
1540
+ * HTML element in an HTML document and so silently misses a case-preserved
1541
+ * handler (e.g. `ONERROR` off an XML/XHTML import) - the same defect
1542
+ * _removeAttribute() was fixed for, which a name-based call would reintroduce
1543
+ * on these IN_PLACE teardown paths. Unlike _removeAttribute this does not
1544
+ * record into DOMPurify.removed: the neutralize passes intentionally do not
1545
+ * book-keep. A clobbered/detached node falls back to best-effort name-based
1546
+ * removal.
1547
+ *
1548
+ * @param element the element to strip the attribute from
1549
+ * @param attribute the Attr node to remove
1550
+ * @param name the attribute's name, for the fallback path
1551
+ */
1552
+ const _stripAttributeNode = function _stripAttributeNode(element, attribute, name) {
1553
+ try {
1554
+ element.removeAttributeNode(attribute);
1555
+ } catch (_) {
1556
+ try {
1557
+ element.removeAttribute(name);
1558
+ } catch (_) {}
1559
+ }
1560
+ };
1561
+ /**
1500
1562
  * _neutralizeRoot
1501
1563
  *
1502
1564
  * Fail-closed teardown of an in-place root after the sanitize walk aborts
@@ -1530,30 +1592,45 @@ function createDOMPurify() {
1530
1592
  if (attributes) for (let i = attributes.length - 1; i >= 0; --i) {
1531
1593
  const attribute = attributes[i];
1532
1594
  const name = attribute && attribute.name;
1533
- if (typeof name === "string") try {
1534
- root.removeAttribute(name);
1535
- } catch (_) {}
1595
+ if (typeof name === "string") _stripAttributeNode(root, attribute, name);
1536
1596
  }
1537
1597
  };
1538
1598
  /**
1539
1599
  * _removeAttribute
1540
1600
  *
1601
+ * Name-based getAttributeNode()/removeAttribute() ASCII-lowercase their
1602
+ * lookup key for HTML elements in an HTML document, so they silently miss an
1603
+ * attribute whose stored qualified name still contains uppercase ASCII
1604
+ * letters. That happens when the node came from a case-preserving source
1605
+ * (an XML/XHTML document imported via importNode(), or createAttributeNS()),
1606
+ * where e.g. `ONERROR` survives the walk: the policy check lowercases to
1607
+ * `onerror` and rejects it, but `removeAttribute('ONERROR')` looks up
1608
+ * `onerror` and finds nothing. Remove the exact Attr node instead, which is
1609
+ * case- and namespace-exact, and fall back to name-based removal only when
1610
+ * the caller could not supply the node.
1611
+ *
1541
1612
  * @param name an Attribute name
1542
1613
  * @param element a DOM node
1614
+ * @param attr the exact Attr node to remove, when the caller has it
1543
1615
  */
1544
- const _removeAttribute = function _removeAttribute(name, element) {
1616
+ const _removeAttribute = function _removeAttribute(name, element, attr) {
1617
+ if (!attr) try {
1618
+ attr = element.getAttributeNode(name);
1619
+ } catch (_) {
1620
+ attr = null;
1621
+ }
1622
+ arrayPush(DOMPurify.removed, {
1623
+ attribute: attr || null,
1624
+ from: element
1625
+ });
1545
1626
  try {
1546
- arrayPush(DOMPurify.removed, {
1547
- attribute: element.getAttributeNode(name),
1548
- from: element
1549
- });
1627
+ if (attr) element.removeAttributeNode(attr);
1628
+ else element.removeAttribute(name);
1550
1629
  } catch (_) {
1551
- arrayPush(DOMPurify.removed, {
1552
- attribute: null,
1553
- from: element
1554
- });
1630
+ try {
1631
+ element.removeAttribute(name);
1632
+ } catch (_) {}
1555
1633
  }
1556
- element.removeAttribute(name);
1557
1634
  if (name === "is") if (RETURN_DOM || RETURN_DOM_FRAGMENT) try {
1558
1635
  _forceRemove(element);
1559
1636
  } catch (_) {}
@@ -1578,9 +1655,7 @@ function createDOMPurify() {
1578
1655
  const attribute = attributes[i];
1579
1656
  const name = attribute && attribute.name;
1580
1657
  if (typeof name !== "string" || ALLOWED_ATTR[transformCaseFunc(name)]) continue;
1581
- try {
1582
- element.removeAttribute(name);
1583
- } catch (_) {}
1658
+ _stripAttributeNode(element, attribute, name);
1584
1659
  }
1585
1660
  };
1586
1661
  /**
@@ -1609,7 +1684,7 @@ function createDOMPurify() {
1609
1684
  const stack = [root];
1610
1685
  while (stack.length > 0) {
1611
1686
  const node = stack.pop();
1612
- if ((getNodeType ? getNodeType(node) : node.nodeType) === NODE_TYPE.element) _stripDisallowedAttributes(node);
1687
+ if (_readNodeType(node) === NODE_TYPE.element) _stripDisallowedAttributes(node);
1613
1688
  const childNodes = getChildNodes(node);
1614
1689
  if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push(childNodes[i]);
1615
1690
  }
@@ -1649,12 +1724,28 @@ function createDOMPurify() {
1649
1724
  *
1650
1725
  * @param root the in-place root to sweep
1651
1726
  */
1727
+ /**
1728
+ * Central policy for declarative-partial-updates patch-linkage attributes,
1729
+ * shared by the _neutralizePatchLinkage pre-pass and _isValidAttribute so
1730
+ * the two sites cannot drift: `patchsrc` always links, `for` links
1731
+ * everywhere except on <label>/<output>, and the whole policy is gated on
1732
+ * SAFE_FOR_XML (see the rationale block in _isValidAttribute).
1733
+ *
1734
+ * @param lcName the transformCaseFunc'd attribute name
1735
+ * @param lcTag the transformCaseFunc'd tag name of the carrying element
1736
+ * @return true if the attribute is patch linkage and must be dropped
1737
+ */
1738
+ const _isPatchLinkageAttribute = function _isPatchLinkageAttribute(lcName, lcTag) {
1739
+ if (!SAFE_FOR_XML) return false;
1740
+ if (lcName === "patchsrc") return true;
1741
+ return lcName === "for" && lcTag !== "label" && lcTag !== "output";
1742
+ };
1652
1743
  const _neutralizePatchLinkage = function _neutralizePatchLinkage(root) {
1653
1744
  if (!SAFE_FOR_XML) return;
1654
1745
  const stack = [root];
1655
1746
  while (stack.length > 0) {
1656
1747
  const node = stack.pop();
1657
- const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
1748
+ const nodeType = _readNodeType(node);
1658
1749
  if (nodeType === NODE_TYPE.processingInstruction || nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, node.data)) {
1659
1750
  try {
1660
1751
  remove(node);
@@ -1663,10 +1754,10 @@ function createDOMPurify() {
1663
1754
  }
1664
1755
  if (nodeType === NODE_TYPE.element) {
1665
1756
  const element = node;
1666
- const lcTag = transformCaseFunc(getNodeName ? getNodeName(node) : node.nodeName);
1757
+ const lcTag = transformCaseFunc(_readNodeName(node));
1667
1758
  try {
1668
1759
  if (element.hasAttribute && element.hasAttribute("patchsrc")) element.removeAttribute("patchsrc");
1669
- if (element.hasAttribute && element.hasAttribute("for") && lcTag !== "label" && lcTag !== "output") element.removeAttribute("for");
1760
+ if (element.hasAttribute && element.hasAttribute("for") && _isPatchLinkageAttribute("for", lcTag)) element.removeAttribute("for");
1670
1761
  } catch (_) {}
1671
1762
  }
1672
1763
  const childNodes = getChildNodes(node);
@@ -1835,12 +1926,32 @@ function createDOMPurify() {
1835
1926
  */
1836
1927
  const _isUnsafeNode = function _isUnsafeNode(currentNode, tagName) {
1837
1928
  if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.innerHTML)) return true;
1838
- if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === "style" && _isNode(currentNode.firstElementChild)) return true;
1929
+ 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;
1839
1930
  if (currentNode.nodeType === NODE_TYPE.processingInstruction) return true;
1840
1931
  if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, currentNode.data)) return true;
1841
1932
  return false;
1842
1933
  };
1843
1934
  /**
1935
+ * Evaluate a CUSTOM_ELEMENT_HANDLING check (a RegExp or a predicate
1936
+ * function, per the validation in _parseConfig) against a name.
1937
+ * Additional arguments are forwarded to predicate functions - the
1938
+ * attributeNameCheck predicate receives the tag name as its second
1939
+ * argument. A null/absent check never matches.
1940
+ *
1941
+ * @param check the configured tagNameCheck / attributeNameCheck value
1942
+ * @param name the name to test
1943
+ * @param args extra arguments forwarded to a predicate function
1944
+ * @return true if the check matches the name
1945
+ */
1946
+ const _matchesNameCheck = function _matchesNameCheck(check, name) {
1947
+ if (check instanceof RegExp) return regExpTest(check, name);
1948
+ if (check instanceof Function) {
1949
+ for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) args[_key - 2] = arguments[_key];
1950
+ return Boolean(check(name, ...args));
1951
+ }
1952
+ return false;
1953
+ };
1954
+ /**
1844
1955
  * Handle a node whose tag is forbidden or not allowlisted: keep
1845
1956
  * allowed custom elements (false return exits _sanitizeElements
1846
1957
  * early - the namespace and fallback-tag removal checks are
@@ -1858,10 +1969,7 @@ function createDOMPurify() {
1858
1969
  * @return true if the node was removed, false if kept
1859
1970
  */
1860
1971
  const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName, root) {
1861
- if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
1862
- if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
1863
- if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) return false;
1864
- }
1972
+ if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName) && _matchesNameCheck(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
1865
1973
  if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
1866
1974
  const parentNode = getParentNode(currentNode);
1867
1975
  const childNodes = getChildNodes(currentNode);
@@ -1895,6 +2003,42 @@ function createDOMPurify() {
1895
2003
  return set === defaultSet || set === setConfigSet ? clone(set) : set;
1896
2004
  };
1897
2005
  /**
2006
+ * Shared guard for a node that a hook has detached from the walk tree,
2007
+ * used after each element-hook site in _sanitizeElements. Detaching is a
2008
+ * long-standing user pattern (issue #469; draw.io-style foreignObject
2009
+ * filtering). Per the cached, unclobberable parentNode getter the node is
2010
+ * genuinely out of the tree, so it can reach neither the serialized
2011
+ * output nor an IN_PLACE live tree; treat it as removed and stop
2012
+ * processing it. Without this guard, the unsafe-node / namespace checks
2013
+ * would call _forceRemove on a parentless node and hit the REPORT-3
2014
+ * fail-closed throw — which exists for nodes DOMPurify wants gone but
2015
+ * *cannot* detach (clobbered / parentless roots), the opposite of a node
2016
+ * that is already safely gone. The walk root is exempt: a detached
2017
+ * IN_PLACE root is legitimate input and must still be fully sanitized,
2018
+ * and a kill-decision on it must keep hitting the REPORT-3 throw.
2019
+ *
2020
+ * Nodes detached by hooks stay the hook's responsibility for placement:
2021
+ * they are not recorded in DOMPurify.removed, so the post-walk IN_PLACE
2022
+ * pass (which iterates DOMPurify.removed) does not reach them. But a
2023
+ * hook-detached subtree can still hold a queued resource-event handler -
2024
+ * e.g. an <img onload> that began loading when the caller built the live
2025
+ * tree - which fires in page scope after sanitize returns even though the
2026
+ * handler never reached the returned tree. That is the audit-5 F1 hazard,
2027
+ * and the documented node.remove() hook pattern walks straight into it.
2028
+ * So on the IN_PLACE path we neutralize the detached subtree inline,
2029
+ * stripping its non-allow-listed attributes before returning, exactly as
2030
+ * the post-walk pass does for _forceRemove'd subtrees.
2031
+ *
2032
+ * @param currentNode the node a hook may have detached
2033
+ * @param root the current walk root
2034
+ * @return true if the node is detached and now handled, false otherwise
2035
+ */
2036
+ const _handleHookDetachedNode = function _handleHookDetachedNode(currentNode, root) {
2037
+ if (currentNode === root || getParentNode(currentNode) !== null) return false;
2038
+ if (IN_PLACE) _neutralizeSubtree(currentNode);
2039
+ return true;
2040
+ };
2041
+ /**
1898
2042
  * _sanitizeElements
1899
2043
  *
1900
2044
  * @protect nodeName
@@ -1905,24 +2049,18 @@ function createDOMPurify() {
1905
2049
  */
1906
2050
  const _sanitizeElements = function _sanitizeElements(currentNode, root) {
1907
2051
  _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
1908
- if (currentNode !== root && getParentNode(currentNode) === null) {
1909
- if (IN_PLACE) _neutralizeSubtree(currentNode);
1910
- return true;
1911
- }
2052
+ if (_handleHookDetachedNode(currentNode, root)) return true;
1912
2053
  if (_isClobbered(currentNode)) {
1913
2054
  _forceRemove(currentNode);
1914
2055
  return true;
1915
2056
  }
1916
- const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
2057
+ const tagName = transformCaseFunc(_readNodeName(currentNode));
1917
2058
  ALLOWED_TAGS = _forkSharedAllowlist(hooks.uponSanitizeElement, ALLOWED_TAGS, DEFAULT_ALLOWED_TAGS, SET_CONFIG_ALLOWED_TAGS);
1918
2059
  _executeHooks(hooks.uponSanitizeElement, currentNode, {
1919
2060
  tagName,
1920
2061
  allowedTags: ALLOWED_TAGS
1921
2062
  });
1922
- if (currentNode !== root && getParentNode(currentNode) === null) {
1923
- if (IN_PLACE) _neutralizeSubtree(currentNode);
1924
- return true;
1925
- }
2063
+ if (_handleHookDetachedNode(currentNode, root)) return true;
1926
2064
  if (_isUnsafeNode(currentNode, tagName)) {
1927
2065
  _forceRemove(currentNode);
1928
2066
  return true;
@@ -1932,7 +2070,7 @@ function createDOMPurify() {
1932
2070
  if (removed === false) _executeHooks(hooks.afterSanitizeElements, currentNode, null);
1933
2071
  return removed;
1934
2072
  }
1935
- if ((getNodeType ? getNodeType(currentNode) : currentNode.nodeType) === NODE_TYPE.element && !_checkValidNamespace(currentNode)) {
2073
+ if (_readNodeType(currentNode) === NODE_TYPE.element && !_checkValidNamespace(currentNode)) {
1936
2074
  _forceRemove(currentNode);
1937
2075
  return true;
1938
2076
  }
@@ -1960,20 +2098,17 @@ function createDOMPurify() {
1960
2098
  */
1961
2099
  const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
1962
2100
  if (FORBID_ATTR[lcName]) return false;
1963
- if (SAFE_FOR_XML && lcName === "patchsrc") return false;
1964
- if (SAFE_FOR_XML && lcName === "for" && lcTag !== "label" && lcTag !== "output") return false;
2101
+ if (_isPatchLinkageAttribute(lcName, lcTag)) return false;
1965
2102
  if (SANITIZE_DOM && (lcName === "id" || lcName === "name") && (value in document || value in formElement)) return false;
1966
2103
  const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
1967
- if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName));
1968
- else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName));
1969
- 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)));
1970
- else return false;
1971
- else if (URI_SAFE_ATTRIBUTES[lcName]);
1972
- else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, "")));
1973
- else if ((lcName === "src" || lcName === "xlink:href" || lcName === "href") && lcTag !== "script" && stringIndexOf(value, "data:") === 0 && DATA_URI_TAGS[lcTag]);
1974
- else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, "")));
1975
- else if (value) return false;
1976
- return true;
2104
+ if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName)) return true;
2105
+ if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) return true;
2106
+ 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);
2107
+ if (URI_SAFE_ATTRIBUTES[lcName]) return true;
2108
+ if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, ""))) return true;
2109
+ if ((lcName === "src" || lcName === "xlink:href" || lcName === "href") && lcTag !== "script" && stringIndexOf(value, "data:") === 0 && DATA_URI_TAGS[lcTag]) return true;
2110
+ if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, ""))) return true;
2111
+ return !value;
1977
2112
  };
1978
2113
  const RESERVED_CUSTOM_ELEMENT_NAMES = addToSet({}, [
1979
2114
  "annotation-xml",
@@ -2075,29 +2210,29 @@ function createDOMPurify() {
2075
2210
  _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
2076
2211
  value = hookEvent.attrValue;
2077
2212
  if (SANITIZE_NAMED_PROPS && (lcName === "id" || lcName === "name") && stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0) {
2078
- _removeAttribute(name, currentNode);
2213
+ _removeAttribute(name, currentNode, attr);
2079
2214
  value = SANITIZE_NAMED_PROPS_PREFIX + value;
2080
2215
  }
2081
2216
  if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
2082
- _removeAttribute(name, currentNode);
2217
+ _removeAttribute(name, currentNode, attr);
2083
2218
  continue;
2084
2219
  }
2085
2220
  if (lcName === "attributename" && stringMatch(value, "href")) {
2086
- _removeAttribute(name, currentNode);
2221
+ _removeAttribute(name, currentNode, attr);
2087
2222
  continue;
2088
2223
  }
2089
2224
  if (hookEvent.forceKeepAttr) continue;
2090
2225
  if (!hookEvent.keepAttr) {
2091
- _removeAttribute(name, currentNode);
2226
+ _removeAttribute(name, currentNode, attr);
2092
2227
  continue;
2093
2228
  }
2094
2229
  if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value)) {
2095
- _removeAttribute(name, currentNode);
2230
+ _removeAttribute(name, currentNode, attr);
2096
2231
  continue;
2097
2232
  }
2098
2233
  if (SAFE_FOR_TEMPLATES) value = _stripTemplateExpressions(value);
2099
2234
  if (!_isValidAttribute(lcTag, lcName, value)) {
2100
- _removeAttribute(name, currentNode);
2235
+ _removeAttribute(name, currentNode, attr);
2101
2236
  continue;
2102
2237
  }
2103
2238
  value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
@@ -2119,7 +2254,7 @@ function createDOMPurify() {
2119
2254
  _sanitizeElements(shadowNode, fragment);
2120
2255
  _sanitizeAttributes(shadowNode);
2121
2256
  if (_isDocumentFragment(shadowNode.content)) _sanitizeShadowDOM2(shadowNode.content);
2122
- if ((getNodeType ? getNodeType(shadowNode) : shadowNode.nodeType) === NODE_TYPE.element) {
2257
+ if (_readNodeType(shadowNode) === NODE_TYPE.element) {
2123
2258
  const innerSr = getShadowRoot(shadowNode);
2124
2259
  if (_isDocumentFragment(innerSr)) {
2125
2260
  _sanitizeAttachedShadowRoots(innerSr);
@@ -2160,7 +2295,7 @@ function createDOMPurify() {
2160
2295
  continue;
2161
2296
  }
2162
2297
  const node = item.node;
2163
- const isElement = (getNodeType ? getNodeType(node) : node.nodeType) === NODE_TYPE.element;
2298
+ const isElement = _readNodeType(node) === NODE_TYPE.element;
2164
2299
  const childNodes = getChildNodes(node);
2165
2300
  if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push({
2166
2301
  node: childNodes[i],
@@ -2211,7 +2346,7 @@ function createDOMPurify() {
2211
2346
  const inPlace = IN_PLACE && typeof dirty !== "string" && _isNode(dirty);
2212
2347
  if (inPlace) {
2213
2348
  _neutralizePatchLinkage(dirty);
2214
- const nn = getNodeName ? getNodeName(dirty) : dirty.nodeName;
2349
+ const nn = _readNodeName(dirty);
2215
2350
  if (typeof nn === "string") {
2216
2351
  const tagName = transformCaseFunc(nn);
2217
2352
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {