@knime/kds-rich-text-editor 10.1.0 → 10.2.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.
package/dist/index.js CHANGED
@@ -9,8 +9,11 @@ import Link from "@tiptap/extension-link";
9
9
  import { Plugin, PluginKey } from "@tiptap/pm/state";
10
10
  import TextAlign from "@tiptap/extension-text-align";
11
11
  import Paragraph from "@tiptap/extension-paragraph";
12
- import './index.css';//#region ../../node_modules/.pnpm/dompurify@3.4.13/node_modules/dompurify/dist/purify.es.mjs
13
- /*! @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 */
12
+ import './index.css';//#region ../../node_modules/.pnpm/dompurify@3.4.16/node_modules/dompurify/dist/purify.es.mjs
13
+ /*! @license DOMPurify 3.4.16 | (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.16/LICENSE */
14
+ function _OverloadYield(e, d) {
15
+ this.v = e, this.k = d;
16
+ }
14
17
  function _arrayLikeToArray(r, a) {
15
18
  (null == a || a > r.length) && (a = r.length);
16
19
  for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
@@ -22,12 +25,14 @@ function _arrayWithHoles(r) {
22
25
  function _iterableToArrayLimit(r, l) {
23
26
  var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
24
27
  if (null != t) {
25
- var e, n, i, u, a = [], f = true, o = false;
28
+ var e, n, i, u, a = [], f = !0, o = !1;
26
29
  try {
27
- if (i = (t = t.call(r)).next, 0 === l);
28
- else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
30
+ if (i = (t = t.call(r)).next, 0 === l) {
31
+ if (Object(t) !== t) return;
32
+ f = !1;
33
+ } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
29
34
  } catch (r) {
30
- o = true, n = r;
35
+ o = !0, n = r;
31
36
  } finally {
32
37
  try {
33
38
  if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
@@ -41,6 +46,7 @@ function _iterableToArrayLimit(r, l) {
41
46
  function _nonIterableRest() {
42
47
  throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
43
48
  }
49
+ /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
44
50
  function _slicedToArray(r, e) {
45
51
  return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
46
52
  }
@@ -51,9 +57,64 @@ function _unsupportedIterableToArray(r, a) {
51
57
  return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
52
58
  }
53
59
  }
54
- var entries = Object.entries, setPrototypeOf = Object.setPrototypeOf, isFrozen = Object.isFrozen, getPrototypeOf = Object.getPrototypeOf, getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
55
- var freeze = Object.freeze, seal = Object.seal, create = Object.create;
56
- var _ref = typeof Reflect !== "undefined" && Reflect, apply = _ref.apply, construct = _ref.construct;
60
+ function AsyncGenerator(e) {
61
+ var t, n;
62
+ function resume(t, n) {
63
+ try {
64
+ var r = e[t](n), o = r.value, u = o instanceof _OverloadYield;
65
+ Promise.resolve(u ? o.v : o).then(function(n) {
66
+ if (u) {
67
+ var i = "return" === t && o.k ? t : "next";
68
+ if (!o.k || n.done) return resume(i, n);
69
+ n = e[i](n).value;
70
+ }
71
+ settle(!!r.done, n);
72
+ }, function(e) {
73
+ resume("throw", e);
74
+ });
75
+ } catch (e) {
76
+ settle(2, e);
77
+ }
78
+ }
79
+ function settle(e, r) {
80
+ 2 === e ? t.reject(r) : t.resolve({
81
+ value: r,
82
+ done: e
83
+ }), (t = t.next) ? resume(t.key, t.arg) : n = null;
84
+ }
85
+ this._invoke = function(e, r) {
86
+ return new Promise(function(o, u) {
87
+ var i = {
88
+ key: e,
89
+ arg: r,
90
+ resolve: o,
91
+ reject: u,
92
+ next: null
93
+ };
94
+ n ? n = n.next = i : (t = n = i, resume(e, r));
95
+ });
96
+ }, "function" != typeof e.return && (this.return = void 0);
97
+ }
98
+ AsyncGenerator.prototype["function" == typeof Symbol && Symbol.asyncIterator || "@@asyncIterator"] = function() {
99
+ return this;
100
+ }, AsyncGenerator.prototype.next = function(e) {
101
+ return this._invoke("next", e);
102
+ }, AsyncGenerator.prototype.throw = function(e) {
103
+ return this._invoke("throw", e);
104
+ }, AsyncGenerator.prototype.return = function(e) {
105
+ return this._invoke("return", e);
106
+ };
107
+ var entries = Object.entries;
108
+ var setPrototypeOf = Object.setPrototypeOf;
109
+ var isFrozen = Object.isFrozen;
110
+ var getPrototypeOf = Object.getPrototypeOf;
111
+ var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
112
+ var freeze = Object.freeze;
113
+ var seal = Object.seal;
114
+ var create = Object.create;
115
+ var _ref = typeof Reflect !== "undefined" && Reflect;
116
+ var apply = _ref.apply;
117
+ var construct = _ref.construct;
57
118
  if (!freeze) freeze = function freeze(x) {
58
119
  return x;
59
120
  };
@@ -69,9 +130,11 @@ if (!construct) construct = function construct(Func) {
69
130
  return new Func(...args);
70
131
  };
71
132
  var arrayForEach = unapply(Array.prototype.forEach);
133
+ Array.prototype.indexOf;
72
134
  var arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
73
135
  var arrayPop = unapply(Array.prototype.pop);
74
136
  var arrayPush = unapply(Array.prototype.push);
137
+ Array.prototype.slice;
75
138
  var arraySplice = unapply(Array.prototype.splice);
76
139
  var arrayIsArray = Array.isArray;
77
140
  var stringToLowerCase = unapply(String.prototype.toLowerCase);
@@ -731,6 +794,7 @@ var svg = freeze([
731
794
  "patterncontentunits",
732
795
  "patterntransform",
733
796
  "patternunits",
797
+ "pointer-events",
734
798
  "points",
735
799
  "preservealpha",
736
800
  "preserveaspectratio",
@@ -785,6 +849,7 @@ var svg = freeze([
785
849
  "u2",
786
850
  "unicode",
787
851
  "values",
852
+ "vector-effect",
788
853
  "viewbox",
789
854
  "visibility",
790
855
  "version",
@@ -898,6 +963,24 @@ var NODE_TYPE = {
898
963
  documentFragment: 11,
899
964
  notation: 12
900
965
  };
966
+ var LITERAL_TEXT_ELEMENT_NAMES = [
967
+ "style",
968
+ "script",
969
+ "xmp",
970
+ "iframe",
971
+ "noembed",
972
+ "noframes",
973
+ "plaintext",
974
+ "noscript"
975
+ ];
976
+ var LITERAL_TEXT_ELEMENTS = freeze(addToSet({}, LITERAL_TEXT_ELEMENT_NAMES));
977
+ var LITERAL_TEXT_CLOSE = function() {
978
+ const map = {};
979
+ arrayForEach(LITERAL_TEXT_ELEMENT_NAMES, (name) => {
980
+ map[name] = seal(new RegExp("</" + name + "(?=[\\t\\n\\f\\r />])", "i"));
981
+ });
982
+ return freeze(map);
983
+ }();
901
984
  var getGlobal = function getGlobal() {
902
985
  return typeof window === "undefined" ? null : window;
903
986
  };
@@ -957,10 +1040,25 @@ var _createHooksMap = function _createHooksMap() {
957
1040
  var _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
958
1041
  return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key]) ? addToSet(options.base ? clone(options.base) : {}, cfg[key], options.transform) : fallback;
959
1042
  };
1043
+ /**
1044
+ * Resolve an object-valued configuration option: a prototype-free clone
1045
+ * of cfg[key] when it is an own, truthy object property, else a fresh
1046
+ * fallback built by makeFallback (fresh on every parse, so a previous
1047
+ * parse can never leak state into the next one).
1048
+ *
1049
+ * @param cfg the cloned, prototype-free configuration object
1050
+ * @param key the configuration property to read
1051
+ * @param makeFallback builds the fallback value when the option is absent
1052
+ * @returns the resolved object
1053
+ */
1054
+ var _resolveObjectOption = function _resolveObjectOption(cfg, key, makeFallback) {
1055
+ const value = objectHasOwnProperty(cfg, key) ? cfg[key] : void 0;
1056
+ return value && typeof value === "object" ? clone(value) : makeFallback();
1057
+ };
960
1058
  function createDOMPurify() {
961
1059
  let window = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : getGlobal();
962
1060
  const DOMPurify = (root) => createDOMPurify(root);
963
- DOMPurify.version = "3.4.13";
1061
+ DOMPurify.version = "3.4.16";
964
1062
  DOMPurify.removed = [];
965
1063
  if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
966
1064
  DOMPurify.isSupported = false;
@@ -977,6 +1075,7 @@ function createDOMPurify() {
977
1075
  const ElementPrototype = Element.prototype;
978
1076
  const cloneNode = lookupGetter(ElementPrototype, "cloneNode");
979
1077
  const remove = lookupGetter(ElementPrototype, "remove");
1078
+ const removeAttributeNode = lookupGetter(ElementPrototype, "removeAttributeNode");
980
1079
  const getNextSibling = lookupGetter(ElementPrototype, "nextSibling");
981
1080
  const getChildNodes = lookupGetter(ElementPrototype, "childNodes");
982
1081
  const getParentNode = lookupGetter(ElementPrototype, "parentNode");
@@ -985,6 +1084,12 @@ function createDOMPurify() {
985
1084
  const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, "nodeType") : null;
986
1085
  const getNodeName = Node && Node.prototype ? lookupGetter(Node.prototype, "nodeName") : null;
987
1086
  const getOwnerDocument = Node && Node.prototype ? lookupGetter(Node.prototype, "ownerDocument") : null;
1087
+ const _readNodeType = function _readNodeType(node) {
1088
+ return getNodeType ? getNodeType(node) : node.nodeType;
1089
+ };
1090
+ const _readNodeName = function _readNodeName(node) {
1091
+ return getNodeName ? getNodeName(node) : node.nodeName;
1092
+ };
988
1093
  if (typeof HTMLTemplateElement === "function") {
989
1094
  const template = document.createElement("template");
990
1095
  if (template.content && template.content.ownerDocument) document = template.content.ownerDocument;
@@ -1242,9 +1347,9 @@ function createDOMPurify() {
1242
1347
  IN_PLACE = cfg.IN_PLACE || false;
1243
1348
  IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI;
1244
1349
  NAMESPACE = typeof cfg.NAMESPACE === "string" ? cfg.NAMESPACE : HTML_NAMESPACE;
1245
- 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);
1246
- 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);
1247
- const customElementHandling = objectHasOwnProperty(cfg, "CUSTOM_ELEMENT_HANDLING") && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === "object" ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create(null);
1350
+ MATHML_TEXT_INTEGRATION_POINTS = _resolveObjectOption(cfg, "MATHML_TEXT_INTEGRATION_POINTS", () => addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS));
1351
+ HTML_INTEGRATION_POINTS = _resolveObjectOption(cfg, "HTML_INTEGRATION_POINTS", () => addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS));
1352
+ const customElementHandling = _resolveObjectOption(cfg, "CUSTOM_ELEMENT_HANDLING", () => create(null));
1248
1353
  CUSTOM_ELEMENT_HANDLING = create(null);
1249
1354
  if (objectHasOwnProperty(customElementHandling, "tagNameCheck") && isRegexOrFunction(customElementHandling.tagNameCheck)) CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck;
1250
1355
  if (objectHasOwnProperty(customElementHandling, "attributeNameCheck") && isRegexOrFunction(customElementHandling.attributeNameCheck)) CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck;
@@ -1291,11 +1396,6 @@ function createDOMPurify() {
1291
1396
  addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
1292
1397
  }
1293
1398
  }
1294
- if (objectHasOwnProperty(cfg, "ADD_URI_SAFE_ATTR") && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
1295
- if (objectHasOwnProperty(cfg, "FORBID_CONTENTS") && arrayIsArray(cfg.FORBID_CONTENTS)) {
1296
- if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) FORBID_CONTENTS = clone(FORBID_CONTENTS);
1297
- addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
1298
- }
1299
1399
  if (objectHasOwnProperty(cfg, "ADD_FORBID_CONTENTS") && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {
1300
1400
  if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) FORBID_CONTENTS = clone(FORBID_CONTENTS);
1301
1401
  addToSet(FORBID_CONTENTS, cfg.ADD_FORBID_CONTENTS, transformCaseFunc);
@@ -1412,6 +1512,32 @@ function createDOMPurify() {
1412
1512
  }
1413
1513
  };
1414
1514
  /**
1515
+ * _stripAttributeNode
1516
+ *
1517
+ * Remove a single Attr node case/namespace-exactly on an attribute-teardown
1518
+ * path. Name-based removeAttribute() ASCII-lowercases its lookup key for an
1519
+ * HTML element in an HTML document and so silently misses a case-preserved
1520
+ * handler (e.g. `ONERROR` off an XML/XHTML import) - the same defect
1521
+ * _removeAttribute() was fixed for, which a name-based call would reintroduce
1522
+ * on these IN_PLACE teardown paths. Unlike _removeAttribute this does not
1523
+ * record into DOMPurify.removed: the neutralize passes intentionally do not
1524
+ * book-keep. A clobbered/detached node falls back to best-effort name-based
1525
+ * removal.
1526
+ *
1527
+ * @param element the element to strip the attribute from
1528
+ * @param attribute the Attr node to remove
1529
+ * @param name the attribute's name, for the fallback path
1530
+ */
1531
+ const _stripAttributeNode = function _stripAttributeNode(element, attribute, name) {
1532
+ try {
1533
+ removeAttributeNode(element, attribute);
1534
+ } catch (_) {
1535
+ try {
1536
+ element.removeAttribute(name);
1537
+ } catch (_) {}
1538
+ }
1539
+ };
1540
+ /**
1415
1541
  * _neutralizeRoot
1416
1542
  *
1417
1543
  * Fail-closed teardown of an in-place root after the sanitize walk aborts
@@ -1445,30 +1571,45 @@ function createDOMPurify() {
1445
1571
  if (attributes) for (let i = attributes.length - 1; i >= 0; --i) {
1446
1572
  const attribute = attributes[i];
1447
1573
  const name = attribute && attribute.name;
1448
- if (typeof name === "string") try {
1449
- root.removeAttribute(name);
1450
- } catch (_) {}
1574
+ if (typeof name === "string") _stripAttributeNode(root, attribute, name);
1451
1575
  }
1452
1576
  };
1453
1577
  /**
1454
1578
  * _removeAttribute
1455
1579
  *
1580
+ * Name-based getAttributeNode()/removeAttribute() ASCII-lowercase their
1581
+ * lookup key for HTML elements in an HTML document, so they silently miss an
1582
+ * attribute whose stored qualified name still contains uppercase ASCII
1583
+ * letters. That happens when the node came from a case-preserving source
1584
+ * (an XML/XHTML document imported via importNode(), or createAttributeNS()),
1585
+ * where e.g. `ONERROR` survives the walk: the policy check lowercases to
1586
+ * `onerror` and rejects it, but `removeAttribute('ONERROR')` looks up
1587
+ * `onerror` and finds nothing. Remove the exact Attr node instead, which is
1588
+ * case- and namespace-exact, and fall back to name-based removal only when
1589
+ * the caller could not supply the node.
1590
+ *
1456
1591
  * @param name an Attribute name
1457
1592
  * @param element a DOM node
1593
+ * @param attr the exact Attr node to remove, when the caller has it
1458
1594
  */
1459
- const _removeAttribute = function _removeAttribute(name, element) {
1595
+ const _removeAttribute = function _removeAttribute(name, element, attr) {
1596
+ if (!attr) try {
1597
+ attr = element.getAttributeNode(name);
1598
+ } catch (_) {
1599
+ attr = null;
1600
+ }
1601
+ arrayPush(DOMPurify.removed, {
1602
+ attribute: attr || null,
1603
+ from: element
1604
+ });
1460
1605
  try {
1461
- arrayPush(DOMPurify.removed, {
1462
- attribute: element.getAttributeNode(name),
1463
- from: element
1464
- });
1606
+ if (attr) removeAttributeNode(element, attr);
1607
+ else element.removeAttribute(name);
1465
1608
  } catch (_) {
1466
- arrayPush(DOMPurify.removed, {
1467
- attribute: null,
1468
- from: element
1469
- });
1609
+ try {
1610
+ element.removeAttribute(name);
1611
+ } catch (_) {}
1470
1612
  }
1471
- element.removeAttribute(name);
1472
1613
  if (name === "is") if (RETURN_DOM || RETURN_DOM_FRAGMENT) try {
1473
1614
  _forceRemove(element);
1474
1615
  } catch (_) {}
@@ -1493,9 +1634,7 @@ function createDOMPurify() {
1493
1634
  const attribute = attributes[i];
1494
1635
  const name = attribute && attribute.name;
1495
1636
  if (typeof name !== "string" || ALLOWED_ATTR[transformCaseFunc(name)]) continue;
1496
- try {
1497
- element.removeAttribute(name);
1498
- } catch (_) {}
1637
+ _stripAttributeNode(element, attribute, name);
1499
1638
  }
1500
1639
  };
1501
1640
  /**
@@ -1524,7 +1663,7 @@ function createDOMPurify() {
1524
1663
  const stack = [root];
1525
1664
  while (stack.length > 0) {
1526
1665
  const node = stack.pop();
1527
- if ((getNodeType ? getNodeType(node) : node.nodeType) === NODE_TYPE.element) _stripDisallowedAttributes(node);
1666
+ if (_readNodeType(node) === NODE_TYPE.element) _stripDisallowedAttributes(node);
1528
1667
  const childNodes = getChildNodes(node);
1529
1668
  if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push(childNodes[i]);
1530
1669
  }
@@ -1564,12 +1703,28 @@ function createDOMPurify() {
1564
1703
  *
1565
1704
  * @param root the in-place root to sweep
1566
1705
  */
1706
+ /**
1707
+ * Central policy for declarative-partial-updates patch-linkage attributes,
1708
+ * shared by the _neutralizePatchLinkage pre-pass and _isValidAttribute so
1709
+ * the two sites cannot drift: `patchsrc` always links, `for` links
1710
+ * everywhere except on <label>/<output>, and the whole policy is gated on
1711
+ * SAFE_FOR_XML (see the rationale block in _isValidAttribute).
1712
+ *
1713
+ * @param lcName the transformCaseFunc'd attribute name
1714
+ * @param lcTag the transformCaseFunc'd tag name of the carrying element
1715
+ * @return true if the attribute is patch linkage and must be dropped
1716
+ */
1717
+ const _isPatchLinkageAttribute = function _isPatchLinkageAttribute(lcName, lcTag) {
1718
+ if (!SAFE_FOR_XML) return false;
1719
+ if (lcName === "patchsrc") return true;
1720
+ return lcName === "for" && lcTag !== "label" && lcTag !== "output";
1721
+ };
1567
1722
  const _neutralizePatchLinkage = function _neutralizePatchLinkage(root) {
1568
1723
  if (!SAFE_FOR_XML) return;
1569
1724
  const stack = [root];
1570
1725
  while (stack.length > 0) {
1571
1726
  const node = stack.pop();
1572
- const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
1727
+ const nodeType = _readNodeType(node);
1573
1728
  if (nodeType === NODE_TYPE.processingInstruction || nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, node.data)) {
1574
1729
  try {
1575
1730
  remove(node);
@@ -1578,10 +1733,10 @@ function createDOMPurify() {
1578
1733
  }
1579
1734
  if (nodeType === NODE_TYPE.element) {
1580
1735
  const element = node;
1581
- const lcTag = transformCaseFunc(getNodeName ? getNodeName(node) : node.nodeName);
1736
+ const lcTag = transformCaseFunc(_readNodeName(node));
1582
1737
  try {
1583
1738
  if (element.hasAttribute && element.hasAttribute("patchsrc")) element.removeAttribute("patchsrc");
1584
- if (element.hasAttribute && element.hasAttribute("for") && lcTag !== "label" && lcTag !== "output") element.removeAttribute("for");
1739
+ if (element.hasAttribute && element.hasAttribute("for") && _isPatchLinkageAttribute("for", lcTag)) element.removeAttribute("for");
1585
1740
  } catch (_) {}
1586
1741
  }
1587
1742
  const childNodes = getChildNodes(node);
@@ -1692,7 +1847,7 @@ function createDOMPurify() {
1692
1847
  const realTagName = getNodeName ? getNodeName(element) : null;
1693
1848
  if (typeof realTagName !== "string") return false;
1694
1849
  if (transformCaseFunc(realTagName) !== "form") return false;
1695
- return typeof element.nodeName !== "string" || typeof element.textContent !== "string" || typeof element.removeChild !== "function" || element.attributes !== getAttributes(element) || typeof element.removeAttribute !== "function" || typeof element.setAttribute !== "function" || typeof element.namespaceURI !== "string" || typeof element.insertBefore !== "function" || typeof element.hasChildNodes !== "function" || element.nodeType !== getNodeType(element) || element.childNodes !== getChildNodes(element);
1850
+ return typeof element.nodeName !== "string" || typeof element.textContent !== "string" || typeof element.removeChild !== "function" || element.attributes !== getAttributes(element) || typeof element.removeAttribute !== "function" || typeof element.removeAttributeNode !== "function" || typeof element.getAttributeNode !== "function" || typeof element.setAttribute !== "function" || typeof element.namespaceURI !== "string" || typeof element.insertBefore !== "function" || typeof element.hasChildNodes !== "function" || element.nodeType !== getNodeType(element) || element.childNodes !== getChildNodes(element);
1696
1851
  };
1697
1852
  /**
1698
1853
  * Checks whether the given value is a DocumentFragment from any realm.
@@ -1750,12 +1905,32 @@ function createDOMPurify() {
1750
1905
  */
1751
1906
  const _isUnsafeNode = function _isUnsafeNode(currentNode, tagName) {
1752
1907
  if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.innerHTML)) return true;
1753
- if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === "style" && _isNode(currentNode.firstElementChild)) return true;
1908
+ 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;
1754
1909
  if (currentNode.nodeType === NODE_TYPE.processingInstruction) return true;
1755
1910
  if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, currentNode.data)) return true;
1756
1911
  return false;
1757
1912
  };
1758
1913
  /**
1914
+ * Evaluate a CUSTOM_ELEMENT_HANDLING check (a RegExp or a predicate
1915
+ * function, per the validation in _parseConfig) against a name.
1916
+ * Additional arguments are forwarded to predicate functions - the
1917
+ * attributeNameCheck predicate receives the tag name as its second
1918
+ * argument. A null/absent check never matches.
1919
+ *
1920
+ * @param check the configured tagNameCheck / attributeNameCheck value
1921
+ * @param name the name to test
1922
+ * @param args extra arguments forwarded to a predicate function
1923
+ * @return true if the check matches the name
1924
+ */
1925
+ const _matchesNameCheck = function _matchesNameCheck(check, name) {
1926
+ if (check instanceof RegExp) return regExpTest(check, name);
1927
+ if (check instanceof Function) {
1928
+ for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) args[_key - 2] = arguments[_key];
1929
+ return Boolean(check(name, ...args));
1930
+ }
1931
+ return false;
1932
+ };
1933
+ /**
1759
1934
  * Handle a node whose tag is forbidden or not allowlisted: keep
1760
1935
  * allowed custom elements (false return exits _sanitizeElements
1761
1936
  * early - the namespace and fallback-tag removal checks are
@@ -1773,10 +1948,7 @@ function createDOMPurify() {
1773
1948
  * @return true if the node was removed, false if kept
1774
1949
  */
1775
1950
  const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName, root) {
1776
- if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
1777
- if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
1778
- if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) return false;
1779
- }
1951
+ if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName) && _matchesNameCheck(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
1780
1952
  if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
1781
1953
  const parentNode = getParentNode(currentNode);
1782
1954
  const childNodes = getChildNodes(currentNode);
@@ -1810,6 +1982,42 @@ function createDOMPurify() {
1810
1982
  return set === defaultSet || set === setConfigSet ? clone(set) : set;
1811
1983
  };
1812
1984
  /**
1985
+ * Shared guard for a node that a hook has detached from the walk tree,
1986
+ * used after each element-hook site in _sanitizeElements. Detaching is a
1987
+ * long-standing user pattern (issue #469; draw.io-style foreignObject
1988
+ * filtering). Per the cached, unclobberable parentNode getter the node is
1989
+ * genuinely out of the tree, so it can reach neither the serialized
1990
+ * output nor an IN_PLACE live tree; treat it as removed and stop
1991
+ * processing it. Without this guard, the unsafe-node / namespace checks
1992
+ * would call _forceRemove on a parentless node and hit the REPORT-3
1993
+ * fail-closed throw — which exists for nodes DOMPurify wants gone but
1994
+ * *cannot* detach (clobbered / parentless roots), the opposite of a node
1995
+ * that is already safely gone. The walk root is exempt: a detached
1996
+ * IN_PLACE root is legitimate input and must still be fully sanitized,
1997
+ * and a kill-decision on it must keep hitting the REPORT-3 throw.
1998
+ *
1999
+ * Nodes detached by hooks stay the hook's responsibility for placement:
2000
+ * they are not recorded in DOMPurify.removed, so the post-walk IN_PLACE
2001
+ * pass (which iterates DOMPurify.removed) does not reach them. But a
2002
+ * hook-detached subtree can still hold a queued resource-event handler -
2003
+ * e.g. an <img onload> that began loading when the caller built the live
2004
+ * tree - which fires in page scope after sanitize returns even though the
2005
+ * handler never reached the returned tree. That is the audit-5 F1 hazard,
2006
+ * and the documented node.remove() hook pattern walks straight into it.
2007
+ * So on the IN_PLACE path we neutralize the detached subtree inline,
2008
+ * stripping its non-allow-listed attributes before returning, exactly as
2009
+ * the post-walk pass does for _forceRemove'd subtrees.
2010
+ *
2011
+ * @param currentNode the node a hook may have detached
2012
+ * @param root the current walk root
2013
+ * @return true if the node is detached and now handled, false otherwise
2014
+ */
2015
+ const _handleHookDetachedNode = function _handleHookDetachedNode(currentNode, root) {
2016
+ if (currentNode === root || getParentNode(currentNode) !== null) return false;
2017
+ if (IN_PLACE) _neutralizeSubtree(currentNode);
2018
+ return true;
2019
+ };
2020
+ /**
1813
2021
  * _sanitizeElements
1814
2022
  *
1815
2023
  * @protect nodeName
@@ -1820,34 +2028,31 @@ function createDOMPurify() {
1820
2028
  */
1821
2029
  const _sanitizeElements = function _sanitizeElements(currentNode, root) {
1822
2030
  _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
1823
- if (currentNode !== root && getParentNode(currentNode) === null) {
1824
- if (IN_PLACE) _neutralizeSubtree(currentNode);
1825
- return true;
1826
- }
2031
+ if (_handleHookDetachedNode(currentNode, root)) return true;
1827
2032
  if (_isClobbered(currentNode)) {
1828
2033
  _forceRemove(currentNode);
1829
2034
  return true;
1830
2035
  }
1831
- const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
2036
+ const tagName = transformCaseFunc(_readNodeName(currentNode));
1832
2037
  ALLOWED_TAGS = _forkSharedAllowlist(hooks.uponSanitizeElement, ALLOWED_TAGS, DEFAULT_ALLOWED_TAGS, SET_CONFIG_ALLOWED_TAGS);
1833
2038
  _executeHooks(hooks.uponSanitizeElement, currentNode, {
1834
2039
  tagName,
1835
2040
  allowedTags: ALLOWED_TAGS
1836
2041
  });
1837
- if (currentNode !== root && getParentNode(currentNode) === null) {
1838
- if (IN_PLACE) _neutralizeSubtree(currentNode);
1839
- return true;
1840
- }
2042
+ if (_handleHookDetachedNode(currentNode, root)) return true;
1841
2043
  if (_isUnsafeNode(currentNode, tagName)) {
1842
2044
  _forceRemove(currentNode);
1843
2045
  return true;
1844
2046
  }
1845
2047
  if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
1846
2048
  const removed = _sanitizeDisallowedNode(currentNode, tagName, root);
1847
- if (removed === false) _executeHooks(hooks.afterSanitizeElements, currentNode, null);
2049
+ if (removed === false) {
2050
+ _executeHooks(hooks.afterSanitizeElements, currentNode, null);
2051
+ if (_handleHookDetachedNode(currentNode, root)) return true;
2052
+ }
1848
2053
  return removed;
1849
2054
  }
1850
- if ((getNodeType ? getNodeType(currentNode) : currentNode.nodeType) === NODE_TYPE.element && !_checkValidNamespace(currentNode)) {
2055
+ if (_readNodeType(currentNode) === NODE_TYPE.element && !_checkValidNamespace(currentNode)) {
1851
2056
  _forceRemove(currentNode);
1852
2057
  return true;
1853
2058
  }
@@ -1863,7 +2068,7 @@ function createDOMPurify() {
1863
2068
  }
1864
2069
  }
1865
2070
  _executeHooks(hooks.afterSanitizeElements, currentNode, null);
1866
- return false;
2071
+ return _handleHookDetachedNode(currentNode, root);
1867
2072
  };
1868
2073
  /**
1869
2074
  * _isValidAttribute
@@ -1875,20 +2080,17 @@ function createDOMPurify() {
1875
2080
  */
1876
2081
  const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
1877
2082
  if (FORBID_ATTR[lcName]) return false;
1878
- if (SAFE_FOR_XML && lcName === "patchsrc") return false;
1879
- if (SAFE_FOR_XML && lcName === "for" && lcTag !== "label" && lcTag !== "output") return false;
2083
+ if (_isPatchLinkageAttribute(lcName, lcTag)) return false;
1880
2084
  if (SANITIZE_DOM && (lcName === "id" || lcName === "name") && (value in document || value in formElement)) return false;
1881
2085
  const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
1882
- if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName));
1883
- else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName));
1884
- 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)));
1885
- else return false;
1886
- else if (URI_SAFE_ATTRIBUTES[lcName]);
1887
- else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, "")));
1888
- else if ((lcName === "src" || lcName === "xlink:href" || lcName === "href") && lcTag !== "script" && stringIndexOf(value, "data:") === 0 && DATA_URI_TAGS[lcTag]);
1889
- else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, "")));
1890
- else if (value) return false;
1891
- return true;
2086
+ if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName)) return true;
2087
+ if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) return true;
2088
+ 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);
2089
+ if (URI_SAFE_ATTRIBUTES[lcName]) return true;
2090
+ if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, ""))) return true;
2091
+ if ((lcName === "src" || lcName === "xlink:href" || lcName === "href") && lcTag !== "script" && stringIndexOf(value, "data:") === 0 && DATA_URI_TAGS[lcTag]) return true;
2092
+ if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, ""))) return true;
2093
+ return !value;
1892
2094
  };
1893
2095
  const RESERVED_CUSTOM_ELEMENT_NAMES = addToSet({}, [
1894
2096
  "annotation-xml",
@@ -1933,24 +2135,38 @@ function createDOMPurify() {
1933
2135
  /**
1934
2136
  * Write a modified attribute value back onto the element. On
1935
2137
  * success, re-probe for clobbering introduced by the new value and
1936
- * remove the element when found; otherwise pop the removal entry
1937
- * recorded by the earlier _removeAttribute (long-standing pairing
1938
- * with the SANITIZE_NAMED_PROPS path - do not "fix" casually). On
2138
+ * remove the element when found; otherwise, when this writeback is the
2139
+ * recreate half of the SANITIZE_NAMED_PROPS remove-and-recreate, pop the
2140
+ * removal entry that path recorded so it does not show as removed. On
1939
2141
  * failure, remove the attribute instead.
1940
2142
  *
2143
+ * Returns true only on a clean write (the value was set and the new value
2144
+ * introduced no clobbering). The caller uses that, together with its own
2145
+ * knowledge of whether this attribute pushed a DOMPurify.removed record, to
2146
+ * decide whether to pop that record. The pop must happen ONLY for the
2147
+ * named-prop remove-and-recreate; popping on any other value change (trim,
2148
+ * template scrubbing, Trusted Types) would consume an unrelated _forceRemove
2149
+ * subtree-cleanup record and let that detached subtree keep a live event
2150
+ * handler through the IN_PLACE neutralization pass (SO-001).
2151
+ *
1941
2152
  * @param currentNode the element carrying the attribute
1942
2153
  * @param name the attribute name as present on the element
1943
2154
  * @param namespaceURI the attribute's namespace, if any
1944
2155
  * @param value the new attribute value
2156
+ * @return true if the value was written without introducing clobbering
1945
2157
  */
1946
2158
  const _setAttributeValue = function _setAttributeValue(currentNode, name, namespaceURI, value) {
1947
2159
  try {
1948
2160
  if (namespaceURI) currentNode.setAttributeNS(namespaceURI, name, value);
1949
2161
  else currentNode.setAttribute(name, value);
1950
- if (_isClobbered(currentNode)) _forceRemove(currentNode);
1951
- else arrayPop(DOMPurify.removed);
2162
+ if (_isClobbered(currentNode)) {
2163
+ _forceRemove(currentNode);
2164
+ return false;
2165
+ }
2166
+ return true;
1952
2167
  } catch (_) {
1953
2168
  _removeAttribute(name, currentNode);
2169
+ return false;
1954
2170
  }
1955
2171
  };
1956
2172
  /**
@@ -1962,9 +2178,11 @@ function createDOMPurify() {
1962
2178
  * @protect setAttribute
1963
2179
  *
1964
2180
  * @param currentNode to sanitize
2181
+ * @param root the current walk root
1965
2182
  */
1966
- const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
2183
+ const _sanitizeAttributes = function _sanitizeAttributes(currentNode, root) {
1967
2184
  _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
2185
+ if (_handleHookDetachedNode(currentNode, root)) return;
1968
2186
  const attributes = currentNode.attributes;
1969
2187
  if (!attributes || _isClobbered(currentNode)) return;
1970
2188
  ALLOWED_ATTR = _forkSharedAllowlist(hooks.uponSanitizeAttribute, ALLOWED_ATTR, DEFAULT_ALLOWED_ATTR, SET_CONFIG_ALLOWED_ATTR);
@@ -1983,6 +2201,7 @@ function createDOMPurify() {
1983
2201
  const lcName = transformCaseFunc(name);
1984
2202
  const initValue = attrValue;
1985
2203
  let value = name === "value" ? initValue : stringTrim(initValue);
2204
+ let recreatedNamedProp = false;
1986
2205
  hookEvent.attrName = lcName;
1987
2206
  hookEvent.attrValue = value;
1988
2207
  hookEvent.keepAttr = true;
@@ -1990,35 +2209,39 @@ function createDOMPurify() {
1990
2209
  _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
1991
2210
  value = hookEvent.attrValue;
1992
2211
  if (SANITIZE_NAMED_PROPS && (lcName === "id" || lcName === "name") && stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0) {
1993
- _removeAttribute(name, currentNode);
2212
+ _removeAttribute(name, currentNode, attr);
1994
2213
  value = SANITIZE_NAMED_PROPS_PREFIX + value;
2214
+ recreatedNamedProp = true;
1995
2215
  }
1996
2216
  if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
1997
- _removeAttribute(name, currentNode);
2217
+ _removeAttribute(name, currentNode, attr);
1998
2218
  continue;
1999
2219
  }
2000
2220
  if (lcName === "attributename" && stringMatch(value, "href")) {
2001
- _removeAttribute(name, currentNode);
2221
+ _removeAttribute(name, currentNode, attr);
2002
2222
  continue;
2003
2223
  }
2004
2224
  if (hookEvent.forceKeepAttr) continue;
2005
2225
  if (!hookEvent.keepAttr) {
2006
- _removeAttribute(name, currentNode);
2226
+ _removeAttribute(name, currentNode, attr);
2007
2227
  continue;
2008
2228
  }
2009
2229
  if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value)) {
2010
- _removeAttribute(name, currentNode);
2230
+ _removeAttribute(name, currentNode, attr);
2011
2231
  continue;
2012
2232
  }
2013
2233
  if (SAFE_FOR_TEMPLATES) value = _stripTemplateExpressions(value);
2014
2234
  if (!_isValidAttribute(lcTag, lcName, value)) {
2015
- _removeAttribute(name, currentNode);
2235
+ _removeAttribute(name, currentNode, attr);
2016
2236
  continue;
2017
2237
  }
2018
2238
  value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
2019
- if (value !== initValue) _setAttributeValue(currentNode, name, namespaceURI, value);
2239
+ if (value !== initValue) {
2240
+ if (_setAttributeValue(currentNode, name, namespaceURI, value) && recreatedNamedProp) arrayPop(DOMPurify.removed);
2241
+ }
2020
2242
  }
2021
2243
  _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
2244
+ _handleHookDetachedNode(currentNode, root);
2022
2245
  };
2023
2246
  /**
2024
2247
  * _sanitizeShadowDOM
@@ -2032,9 +2255,9 @@ function createDOMPurify() {
2032
2255
  while (shadowNode = shadowIterator.nextNode()) {
2033
2256
  _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
2034
2257
  _sanitizeElements(shadowNode, fragment);
2035
- _sanitizeAttributes(shadowNode);
2258
+ _sanitizeAttributes(shadowNode, fragment);
2036
2259
  if (_isDocumentFragment(shadowNode.content)) _sanitizeShadowDOM2(shadowNode.content);
2037
- if ((getNodeType ? getNodeType(shadowNode) : shadowNode.nodeType) === NODE_TYPE.element) {
2260
+ if (_readNodeType(shadowNode) === NODE_TYPE.element) {
2038
2261
  const innerSr = getShadowRoot(shadowNode);
2039
2262
  if (_isDocumentFragment(innerSr)) {
2040
2263
  _sanitizeAttachedShadowRoots(innerSr);
@@ -2075,7 +2298,7 @@ function createDOMPurify() {
2075
2298
  continue;
2076
2299
  }
2077
2300
  const node = item.node;
2078
- const isElement = (getNodeType ? getNodeType(node) : node.nodeType) === NODE_TYPE.element;
2301
+ const isElement = _readNodeType(node) === NODE_TYPE.element;
2079
2302
  const childNodes = getChildNodes(node);
2080
2303
  if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push({
2081
2304
  node: childNodes[i],
@@ -2126,7 +2349,7 @@ function createDOMPurify() {
2126
2349
  const inPlace = IN_PLACE && typeof dirty !== "string" && _isNode(dirty);
2127
2350
  if (inPlace) {
2128
2351
  _neutralizePatchLinkage(dirty);
2129
- const nn = getNodeName ? getNodeName(dirty) : dirty.nodeName;
2352
+ const nn = _readNodeName(dirty);
2130
2353
  if (typeof nn === "string") {
2131
2354
  const tagName = transformCaseFunc(nn);
2132
2355
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
@@ -2150,7 +2373,7 @@ function createDOMPurify() {
2150
2373
  if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === "BODY") body = importedNode;
2151
2374
  else if (importedNode.nodeName === "HTML") body = importedNode;
2152
2375
  else body.appendChild(importedNode);
2153
- _sanitizeAttachedShadowRoots(importedNode);
2376
+ _sanitizeAttachedShadowRoots(body);
2154
2377
  } else {
2155
2378
  if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && dirty.indexOf("<") === -1) return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(dirty) : dirty;
2156
2379
  body = _initDocument(dirty);
@@ -2162,7 +2385,7 @@ function createDOMPurify() {
2162
2385
  const nodeIterator = _createNodeIterator(walkRoot);
2163
2386
  while (currentNode = nodeIterator.nextNode()) {
2164
2387
  _sanitizeElements(currentNode, walkRoot);
2165
- _sanitizeAttributes(currentNode);
2388
+ _sanitizeAttributes(currentNode, walkRoot);
2166
2389
  if (_isDocumentFragment(currentNode.content)) _sanitizeShadowDOM2(currentNode.content);
2167
2390
  }
2168
2391
  } catch (error) {
@@ -2175,9 +2398,14 @@ function createDOMPurify() {
2175
2398
  throw error;
2176
2399
  }
2177
2400
  if (inPlace) {
2401
+ let rootWasRemoved = false;
2178
2402
  arrayForEach(DOMPurify.removed, (entry) => {
2179
- if (entry.element) _neutralizeSubtree(entry.element);
2403
+ if (entry.element) {
2404
+ if (entry.element === dirty) rootWasRemoved = true;
2405
+ _neutralizeSubtree(entry.element);
2406
+ }
2180
2407
  });
2408
+ if (rootWasRemoved) throw typeErrorCreate("a node selected for removal could not be safely returned; refusing to sanitize in place");
2181
2409
  if (SAFE_FOR_TEMPLATES) _scrubTemplateExpressions2(dirty);
2182
2410
  return dirty;
2183
2411
  }