@aicayzer/inkkit 0.1.5 → 0.1.6

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.
@@ -480,7 +480,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
480
480
 
481
481
  ------------------------------------------------------------------------
482
482
 
483
- dompurify@3.4.16 ((MPL-2.0 OR Apache-2.0))
483
+ dompurify@3.4.17 ((MPL-2.0 OR Apache-2.0))
484
484
 
485
485
  Apache License
486
486
  Version 2.0, January 2004
@@ -28391,8 +28391,8 @@ function editorLabels(ctx) {
28391
28391
  return ctx.isInjected(labelsCtx.key) ? ctx.get(labelsCtx.key) : defaultLabels;
28392
28392
  }
28393
28393
  //#endregion
28394
- //#region node_modules/.pnpm/dompurify@3.4.16/node_modules/dompurify/dist/purify.es.mjs
28395
- /*! @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 */
28394
+ //#region node_modules/.pnpm/dompurify@3.4.17/node_modules/dompurify/dist/purify.es.mjs
28395
+ /*! @license DOMPurify 3.4.17 | (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.17/LICENSE */
28396
28396
  function _OverloadYield(e, d) {
28397
28397
  this.v = e, this.k = d;
28398
28398
  }
@@ -28516,9 +28516,12 @@ Array.prototype.indexOf;
28516
28516
  var arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
28517
28517
  var arrayPop = unapply(Array.prototype.pop);
28518
28518
  var arrayPush = unapply(Array.prototype.push);
28519
- Array.prototype.slice;
28519
+ var arraySlice = unapply(Array.prototype.slice);
28520
28520
  var arraySplice = unapply(Array.prototype.splice);
28521
28521
  var arrayIsArray = Array.isArray;
28522
+ var setCreate = unconstruct(Set);
28523
+ var setAdd = unapply(Set.prototype.add);
28524
+ var setHas = unapply(Set.prototype.has);
28522
28525
  var stringToLowerCase = unapply(String.prototype.toLowerCase);
28523
28526
  var stringToString = unapply(String.prototype.toString);
28524
28527
  var stringMatch = unapply(String.prototype.match);
@@ -28663,6 +28666,25 @@ function lookupGetter(object, prop) {
28663
28666
  }
28664
28667
  return fallbackValue;
28665
28668
  }
28669
+ /**
28670
+ * Like lookupGetter, but for accessor properties only, and returns null
28671
+ * instead of a null-returning fallback when no getter exists on the chain.
28672
+ * Lets a caller fall back to a direct property read in environments that
28673
+ * lack the getter, where a fallback returning null would be indistinguishable
28674
+ * from a legitimate null value (e.g. namespaceURI of a no-namespace element).
28675
+ *
28676
+ * @param object - The object (usually a prototype) to start the lookup at.
28677
+ * @param prop - The accessor property name.
28678
+ * @returns The unapplied getter, or null if none was found.
28679
+ */
28680
+ function lookupAccessor(object, prop) {
28681
+ while (object !== null) {
28682
+ const desc = getOwnPropertyDescriptor(object, prop);
28683
+ if (desc) return desc.get ? unapply(desc.get) : null;
28684
+ object = getPrototypeOf(object);
28685
+ }
28686
+ return null;
28687
+ }
28666
28688
  function isRegex(value) {
28667
28689
  try {
28668
28690
  regExpTest(value, "");
@@ -29347,7 +29369,7 @@ var NODE_TYPE = {
29347
29369
  documentFragment: 11,
29348
29370
  notation: 12
29349
29371
  };
29350
- var LITERAL_TEXT_ELEMENT_NAMES = [
29372
+ var LITERAL_TEXT_ELEMENTS = freeze(addToSet({}, [
29351
29373
  "style",
29352
29374
  "script",
29353
29375
  "xmp",
@@ -29356,15 +29378,7 @@ var LITERAL_TEXT_ELEMENT_NAMES = [
29356
29378
  "noframes",
29357
29379
  "plaintext",
29358
29380
  "noscript"
29359
- ];
29360
- var LITERAL_TEXT_ELEMENTS = freeze(addToSet({}, LITERAL_TEXT_ELEMENT_NAMES));
29361
- var LITERAL_TEXT_CLOSE = function() {
29362
- const map = {};
29363
- arrayForEach(LITERAL_TEXT_ELEMENT_NAMES, (name) => {
29364
- map[name] = seal(new RegExp("</" + name + "(?=[\\t\\n\\f\\r />])", "i"));
29365
- });
29366
- return freeze(map);
29367
- }();
29381
+ ]));
29368
29382
  var getGlobal = function getGlobal() {
29369
29383
  return typeof window === "undefined" ? null : window;
29370
29384
  };
@@ -29442,7 +29456,7 @@ var _resolveObjectOption = function _resolveObjectOption(cfg, key, makeFallback)
29442
29456
  function createDOMPurify() {
29443
29457
  let window = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : getGlobal();
29444
29458
  const DOMPurify = (root) => createDOMPurify(root);
29445
- DOMPurify.version = "3.4.16";
29459
+ DOMPurify.version = "3.4.17";
29446
29460
  DOMPurify.removed = [];
29447
29461
  if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
29448
29462
  DOMPurify.isSupported = false;
@@ -29474,6 +29488,10 @@ function createDOMPurify() {
29474
29488
  const _readNodeName = function _readNodeName(node) {
29475
29489
  return getNodeName ? getNodeName(node) : node.nodeName;
29476
29490
  };
29491
+ const getNamespaceURI = lookupAccessor(ElementPrototype, "namespaceURI");
29492
+ const _readNamespaceURI = function _readNamespaceURI(element) {
29493
+ return getNamespaceURI ? getNamespaceURI(element) : element.namespaceURI;
29494
+ };
29477
29495
  if (typeof HTMLTemplateElement === "function") {
29478
29496
  const template = document.createElement("template");
29479
29497
  if (template.content && template.content.ownerDocument) document = template.content.ownerDocument;
@@ -29594,6 +29612,7 @@ function createDOMPurify() {
29594
29612
  const SANITIZE_NAMED_PROPS_PREFIX = "user-content-";
29595
29613
  let KEEP_CONTENT = true;
29596
29614
  let IN_PLACE = false;
29615
+ let IN_PLACE_VISITED = null;
29597
29616
  let USE_PROFILES = {};
29598
29617
  let FORBID_CONTENTS = null;
29599
29618
  const DEFAULT_FORBID_CONTENTS = addToSet({}, [
@@ -29867,18 +29886,23 @@ function createDOMPurify() {
29867
29886
  * return. Return true otherwise.
29868
29887
  */
29869
29888
  const _checkValidNamespace = function _checkValidNamespace(element) {
29870
- let parent = getParentNode(element);
29871
- if (!parent || !parent.tagName) parent = {
29872
- namespaceURI: NAMESPACE,
29873
- tagName: "template"
29874
- };
29875
- const tagName = stringToLowerCase(element.tagName);
29876
- const parentTagName = stringToLowerCase(parent.tagName);
29877
- if (!ALLOWED_NAMESPACES[element.namespaceURI]) return false;
29878
- if (element.namespaceURI === SVG_NAMESPACE) return _checkSvgNamespace(tagName, parent, parentTagName);
29879
- if (element.namespaceURI === MATHML_NAMESPACE) return _checkMathMlNamespace(tagName, parent, parentTagName);
29880
- if (element.namespaceURI === HTML_NAMESPACE) return _checkHtmlNamespace(tagName, parent, parentTagName);
29881
- if (PARSER_MEDIA_TYPE === "application/xhtml+xml" && ALLOWED_NAMESPACES[element.namespaceURI]) return true;
29889
+ const elementNamespace = _readNamespaceURI(element);
29890
+ const tagName = stringToLowerCase(_readNodeName(element));
29891
+ const parentNode = getParentNode(element);
29892
+ let parent;
29893
+ let parentTagName;
29894
+ if (parentNode && _readNodeType(parentNode) === NODE_TYPE.element) {
29895
+ parent = { namespaceURI: _readNamespaceURI(parentNode) };
29896
+ parentTagName = stringToLowerCase(_readNodeName(parentNode));
29897
+ } else {
29898
+ parent = { namespaceURI: NAMESPACE };
29899
+ parentTagName = "template";
29900
+ }
29901
+ if (!ALLOWED_NAMESPACES[elementNamespace]) return false;
29902
+ if (elementNamespace === SVG_NAMESPACE) return _checkSvgNamespace(tagName, parent, parentTagName);
29903
+ if (elementNamespace === MATHML_NAMESPACE) return _checkMathMlNamespace(tagName, parent, parentTagName);
29904
+ if (elementNamespace === HTML_NAMESPACE) return _checkHtmlNamespace(tagName, parent, parentTagName);
29905
+ if (PARSER_MEDIA_TYPE === "application/xhtml+xml" && ALLOWED_NAMESPACES[elementNamespace]) return true;
29882
29906
  return false;
29883
29907
  };
29884
29908
  /**
@@ -29922,36 +29946,21 @@ function createDOMPurify() {
29922
29946
  }
29923
29947
  };
29924
29948
  /**
29925
- * _neutralizeRoot
29926
- *
29927
- * Fail-closed teardown of an in-place root after the sanitize walk aborts
29928
- * (campaign-3 F2). An internal throw mid-walk — e.g. a page-registered
29929
- * custom element's reaction detaches a node so `_forceRemove`'s deliberate
29930
- * parentless guard throws, or any other re-entrant engine mutation — would
29931
- * otherwise leave the caller's *live* tree half-sanitized, with everything
29932
- * after the abort point still carrying its handlers. There is no safe way
29933
- * to resume the walk (the tree mutated under us), so we strip the root bare:
29934
- * remove every child and every attribute, then let the caller's catch see
29935
- * the original error. Clobber-safe (cached `remove`/`childNodes`/`attributes`
29936
- * getters; the root was already clobber-pre-flighted at the IN_PLACE entry).
29949
+ * Fail-closed teardown of an in-place root: strip every disallowed attribute
29950
+ * in the subtree first, so nothing armed is left behind, then remove all
29951
+ * children and the root's own attributes.
29937
29952
  *
29938
29953
  * @param root the in-place root to empty
29939
29954
  */
29940
29955
  const _neutralizeRoot = function _neutralizeRoot(root) {
29941
29956
  _neutralizeSubtree(root);
29942
29957
  const childNodes = getChildNodes(root);
29943
- if (childNodes) {
29944
- const snapshot = [];
29945
- arrayForEach(childNodes, (child) => {
29946
- arrayPush(snapshot, child);
29947
- });
29948
- arrayForEach(snapshot, (child) => {
29949
- try {
29950
- remove(child);
29951
- } catch (_) {}
29952
- });
29953
- }
29954
- const attributes = getAttributes(root);
29958
+ if (childNodes) arrayForEach(arraySlice(childNodes), (child) => {
29959
+ try {
29960
+ remove(child);
29961
+ } catch (_) {}
29962
+ });
29963
+ const attributes = _readNodeType(root) === NODE_TYPE.element ? getAttributes(root) : null;
29955
29964
  if (attributes) for (let i = attributes.length - 1; i >= 0; --i) {
29956
29965
  const attribute = attributes[i];
29957
29966
  const name = attribute && attribute.name;
@@ -29959,18 +29968,9 @@ function createDOMPurify() {
29959
29968
  }
29960
29969
  };
29961
29970
  /**
29962
- * _removeAttribute
29963
- *
29964
- * Name-based getAttributeNode()/removeAttribute() ASCII-lowercase their
29965
- * lookup key for HTML elements in an HTML document, so they silently miss an
29966
- * attribute whose stored qualified name still contains uppercase ASCII
29967
- * letters. That happens when the node came from a case-preserving source
29968
- * (an XML/XHTML document imported via importNode(), or createAttributeNS()),
29969
- * where e.g. `ONERROR` survives the walk: the policy check lowercases to
29970
- * `onerror` and rejects it, but `removeAttribute('ONERROR')` looks up
29971
- * `onerror` and finds nothing. Remove the exact Attr node instead, which is
29972
- * case- and namespace-exact, and fall back to name-based removal only when
29973
- * the caller could not supply the node.
29971
+ * Removes an attribute, preferably as its exact Attr node: name-based
29972
+ * removal lowercases the name in HTML documents and misses case-preserved
29973
+ * names, e.g. from imported XHTML.
29974
29974
  *
29975
29975
  * @param name an Attribute name
29976
29976
  * @param element a DOM node
@@ -30004,12 +30004,8 @@ function createDOMPurify() {
30004
30004
  }
30005
30005
  };
30006
30006
  /**
30007
- * _stripDisallowedAttributes
30008
- *
30009
- * Removes every attribute the active configuration does not allow from a
30010
- * single element, using the same allowlist as the main attribute pass (so
30011
- * `on*` handlers go, but no `/^on/` blocklist is introduced). Used only to
30012
- * neutralise nodes that are being discarded from an in-place tree.
30007
+ * Removes every attribute the configuration does not allow. Used to
30008
+ * neutralize nodes discarded from an in-place tree.
30013
30009
  *
30014
30010
  * @param element the element to strip
30015
30011
  */
@@ -30024,77 +30020,158 @@ function createDOMPurify() {
30024
30020
  }
30025
30021
  };
30026
30022
  /**
30027
- * _neutralizeSubtree
30023
+ * Iterative depth-first walk, so deep trees cannot overflow the call stack,
30024
+ * reading through the cached getters, so clobbering cannot hide children.
30025
+ * When `visit` returns false, the node's children are skipped. With
30026
+ * `intoShadow`, open shadow roots are walked after their host's children.
30028
30027
  *
30029
- * Completes the audit-5 F1 fix across every removal path. The KEEP_CONTENT
30030
- * move-hoist neutralises only disallowed-tag removals; clobber, mXSS-canary,
30031
- * namespace, comment, processing-instruction and KEEP_CONTENT:false removals
30032
- * all drop their subtree wholesale via `_forceRemove`. On the IN_PLACE path
30033
- * those dropped nodes are detached from the caller's LIVE tree but a
30034
- * handler-bearing original among them (an `<img onerror>`/`<video>` that was
30035
- * loading) keeps its queued resource event, which fires in page scope after
30036
- * sanitize returns. This walks a removed subtree and strips every attribute
30037
- * the active configuration does not allow — so `on*` handlers are cancelled
30038
- * through the SAME allowlist that governs kept nodes, not a separate `/^on/`
30039
- * blocklist. Run synchronously before sanitize returns, i.e. before any
30040
- * queued event can fire. Hook-free by design: these nodes leave the output,
30041
- * so firing attribute hooks for them would be surprising. Clobber-safe reads;
30042
- * a doomed clobbered node may shadow `removeAttribute` (its own attributes are
30043
- * irrelevant — it is discarded — while its non-clobbered descendants, e.g.
30044
- * the `<img>`, are reached and scrubbed).
30045
- *
30046
- * @param root the root of a removed subtree to neutralise
30028
+ * @param root where to start
30029
+ * @param visit called for every node
30030
+ * @param intoShadow whether to enter open shadow roots
30047
30031
  */
30048
- const _neutralizeSubtree = function _neutralizeSubtree(root) {
30032
+ const _walkTree = function _walkTree(root, visit) {
30033
+ let intoShadow = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : false;
30049
30034
  const stack = [root];
30050
30035
  while (stack.length > 0) {
30051
30036
  const node = stack.pop();
30052
- if (_readNodeType(node) === NODE_TYPE.element) _stripDisallowedAttributes(node);
30037
+ if (visit(node) === false) continue;
30038
+ if (intoShadow && _readNodeType(node) === NODE_TYPE.element) {
30039
+ const shadowRoot = getShadowRoot(node);
30040
+ if (_isDocumentFragment(shadowRoot)) stack.push(shadowRoot);
30041
+ }
30053
30042
  const childNodes = getChildNodes(node);
30054
30043
  if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push(childNodes[i]);
30055
30044
  }
30056
30045
  };
30057
30046
  /**
30058
- * _neutralizePatchLinkage
30047
+ * Strips disallowed attributes from every element of a discarded subtree,
30048
+ * so a handler that is already queued (a loading image) cannot fire.
30059
30049
  *
30060
- * IN_PLACE entry pre-pass (declarative-partial-updates / streaming
30061
- * hardening, https://github.com/WICG/declarative-partial-updates).
30050
+ * @param root the root of a removed subtree
30051
+ */
30052
+ const _neutralizeSubtree = function _neutralizeSubtree(root) {
30053
+ _walkTree(root, (node) => {
30054
+ if (_readNodeType(node) === NODE_TYPE.element) _stripDisallowedAttributes(node);
30055
+ });
30056
+ };
30057
+ /**
30058
+ * All nodes under `root`, root first, including open shadow roots. Template
30059
+ * content is not entered, it is inert.
30062
30060
  *
30063
- * The main walk strips patch linkage (`for`/`patchsrc`) and removes range
30064
- * markers (PIs / markup comments) node-by-node, in document order, AS it
30065
- * reaches each node. On a live in-place root that leaves a window: from the
30066
- * moment the root is connected until the walk arrives at a given node, that
30067
- * node's linkage is live. A patch applied on connection/stream can fire as
30068
- * a microtask during the walk and inject or teleport an unsanitized DOM
30069
- * range into a region the iterator has already passed and will not revisit,
30070
- * so the post-return "tree is sanitized" contract is violated. Sweep the
30071
- * whole tree once up front and sever every linkage before the walk begins,
30072
- * closing that window.
30061
+ * @param root the in-place root
30062
+ * @return the nodes
30063
+ */
30064
+ const _collectLiveNodes = function _collectLiveNodes(root) {
30065
+ const nodes = [];
30066
+ _walkTree(root, (node) => {
30067
+ nodes.push(node);
30068
+ }, true);
30069
+ return nodes;
30070
+ };
30071
+ /**
30072
+ * Strips disallowed attributes from every element that was under the
30073
+ * in-place root when the call started and no longer is, whatever removed
30074
+ * or moved it.
30073
30075
  *
30074
- * This CANNOT undo a patch that already fired before sanitize ran — that is
30075
- * the irreducible "do not IN_PLACE a live-connected attacker tree" caveat —
30076
- * but it closes everything from sanitize-start onward. Gated on SAFE_FOR_XML
30077
- * to group with the rest of the declarative-partial-updates handling and
30078
- * stay overridable, consistent with the codebase.
30076
+ * @param root the in-place root
30077
+ * @param before the root's nodes, collected before the walk
30078
+ */
30079
+ const _neutralizeEscapedElements = function _neutralizeEscapedElements(root, before) {
30080
+ const reachable = setCreate();
30081
+ arrayForEach(_collectLiveNodes(root), (node) => {
30082
+ if (_readNodeType(node) === NODE_TYPE.element) setAdd(reachable, node);
30083
+ });
30084
+ arrayForEach(before, (node) => {
30085
+ if (_readNodeType(node) === NODE_TYPE.element && !setHas(reachable, node)) _stripDisallowedAttributes(node);
30086
+ });
30087
+ };
30088
+ /**
30089
+ * Neutralizes everything that left the in-place tree during the call: what
30090
+ * DOMPurify removed, and what hooks or custom elements removed or moved out.
30079
30091
  *
30080
- * Clobber-safe traversal (cached childNodes getter); per-node try/catch so a
30081
- * clobbered root cannot defeat the sweep of its non-clobbered descendants.
30092
+ * @param root the in-place root
30093
+ * @param before the root's nodes, collected before the walk
30094
+ * @return true if the root itself was removed
30095
+ */
30096
+ const _neutralizeDiscarded = function _neutralizeDiscarded(root, before) {
30097
+ let rootRemoved = false;
30098
+ arrayForEach(DOMPurify.removed, (entry) => {
30099
+ if (entry.element) {
30100
+ rootRemoved = rootRemoved || entry.element === root;
30101
+ _neutralizeSubtree(entry.element);
30102
+ }
30103
+ });
30104
+ _neutralizeEscapedElements(root, before);
30105
+ return rootRemoved;
30106
+ };
30107
+ const MAX_UNVISITED_ROUNDS = 8;
30108
+ /**
30109
+ * The walk only moves forward, so a node moved to a position it already
30110
+ * passed is never visited. Give every original node that is still in the
30111
+ * tree but unvisited the normal treatment, hooks included. Nodes created
30112
+ * during the call are left alone. Fails closed if the tree keeps changing.
30113
+ *
30114
+ * @param root the in-place root
30115
+ * @param before the root's nodes, collected before the walk
30116
+ */
30117
+ const _sanitizeUnvisitedNodes = function _sanitizeUnvisitedNodes(root, before) {
30118
+ const visited = IN_PLACE_VISITED;
30119
+ let original = null;
30120
+ const getOriginal = () => {
30121
+ if (!original) {
30122
+ original = setCreate();
30123
+ arrayForEach(before, (beforeNode) => {
30124
+ setAdd(original, beforeNode);
30125
+ });
30126
+ }
30127
+ return original;
30128
+ };
30129
+ for (let round = 0;; round++) {
30130
+ const live = _collectLiveNodes(root);
30131
+ const containers = [root];
30132
+ let pending = false;
30133
+ arrayForEach(live, (node) => {
30134
+ if (_readNodeType(node) === NODE_TYPE.documentFragment) arrayPush(containers, node);
30135
+ else if (!pending && !setHas(visited, node)) pending = setHas(getOriginal(), node);
30136
+ });
30137
+ if (!pending) return;
30138
+ if (round >= MAX_UNVISITED_ROUNDS) throw typeErrorCreate("the tree kept changing during in-place sanitization; refusing to return it");
30139
+ arrayForEach(containers, (container, index) => {
30140
+ _sanitizeUnvisitedIn(container, index > 0, root, getOriginal());
30141
+ });
30142
+ }
30143
+ };
30144
+ /**
30145
+ * One _sanitizeUnvisitedNodes pass over the root or an open shadow root.
30082
30146
  *
30083
- * NOTE (pending real-Chrome confirmation, see test/declarative-patch-probe
30084
- * .html Q1): this mirrors the existing policy of keeping `for` on
30085
- * <label>/<output>. If the shipping feature can drive a patch through a
30086
- * surviving `for`-on-label/output + `id` pair, this pre-pass and the
30087
- * attribute check at _isBasicCustomElement's caller must additionally drop
30088
- * that pair on the IN_PLACE path. Left as-is until the taxonomy is verified.
30147
+ * @param container the root or an open shadow root
30148
+ * @param inShadow whether container is a shadow root
30149
+ * @param root the in-place root
30150
+ * @param original the root's nodes collected before the walk, as a set
30151
+ */
30152
+ const _sanitizeUnvisitedIn = function _sanitizeUnvisitedIn(container, inShadow, root, original) {
30153
+ const iterator = _createNodeIterator(container);
30154
+ let node;
30155
+ while (node = iterator.nextNode()) {
30156
+ if (!setHas(original, node) || setHas(IN_PLACE_VISITED, node)) continue;
30157
+ if (inShadow) _executeHooks(hooks.uponSanitizeShadowNode, node, null);
30158
+ _sanitizeElements(node, root);
30159
+ _sanitizeAttributes(node, root);
30160
+ if (_isDocumentFragment(node.content)) _sanitizeShadowDOM2(node.content);
30161
+ }
30162
+ };
30163
+ /**
30164
+ * Records that _sanitizeElements processed `node`. No-op outside IN_PLACE.
30089
30165
  *
30090
- * @param root the in-place root to sweep
30166
+ * @param node the node being processed
30091
30167
  */
30168
+ const _markVisited = function _markVisited(node) {
30169
+ if (IN_PLACE_VISITED) setAdd(IN_PLACE_VISITED, node);
30170
+ };
30092
30171
  /**
30093
- * Central policy for declarative-partial-updates patch-linkage attributes,
30094
- * shared by the _neutralizePatchLinkage pre-pass and _isValidAttribute so
30095
- * the two sites cannot drift: `patchsrc` always links, `for` links
30096
- * everywhere except on <label>/<output>, and the whole policy is gated on
30097
- * SAFE_FOR_XML (see the rationale block in _isValidAttribute).
30172
+ * Patch-linkage attributes (declarative partial updates): `patchsrc`
30173
+ * always, `for` everywhere except on <label> and <output>. Gated on
30174
+ * SAFE_FOR_XML. Shared by the IN_PLACE pre-pass and _isValidAttribute.
30098
30175
  *
30099
30176
  * @param lcName the transformCaseFunc'd attribute name
30100
30177
  * @param lcTag the transformCaseFunc'd tag name of the carrying element
@@ -30105,17 +30182,26 @@ function createDOMPurify() {
30105
30182
  if (lcName === "patchsrc") return true;
30106
30183
  return lcName === "for" && lcTag !== "label" && lcTag !== "output";
30107
30184
  };
30185
+ /**
30186
+ * IN_PLACE pre-pass: sever all patch linkage before the walk, so no patch
30187
+ * can fire mid-walk into a part of the tree the walker already passed.
30188
+ * Removes range markers (processing instructions, comments with markup)
30189
+ * and strips linkage attributes. It cannot undo a patch that already fired.
30190
+ *
30191
+ * TODO: if the shipped feature can patch through `for` on <label>/<output>,
30192
+ * drop that pair here too (see test/declarative-patch-probe.html).
30193
+ *
30194
+ * @param root the in-place root to sweep
30195
+ */
30108
30196
  const _neutralizePatchLinkage = function _neutralizePatchLinkage(root) {
30109
30197
  if (!SAFE_FOR_XML) return;
30110
- const stack = [root];
30111
- while (stack.length > 0) {
30112
- const node = stack.pop();
30198
+ _walkTree(root, (node) => {
30113
30199
  const nodeType = _readNodeType(node);
30114
30200
  if (nodeType === NODE_TYPE.processingInstruction || nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, node.data)) {
30115
30201
  try {
30116
30202
  remove(node);
30117
30203
  } catch (_) {}
30118
- continue;
30204
+ return false;
30119
30205
  }
30120
30206
  if (nodeType === NODE_TYPE.element) {
30121
30207
  const element = node;
@@ -30125,9 +30211,8 @@ function createDOMPurify() {
30125
30211
  if (element.hasAttribute && element.hasAttribute("for") && _isPatchLinkageAttribute("for", lcTag)) element.removeAttribute("for");
30126
30212
  } catch (_) {}
30127
30213
  }
30128
- const childNodes = getChildNodes(node);
30129
- if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push(childNodes[i]);
30130
- }
30214
+ return true;
30215
+ });
30131
30216
  };
30132
30217
  /**
30133
30218
  * _initDocument
@@ -30204,6 +30289,27 @@ function createDOMPurify() {
30204
30289
  */
30205
30290
  const _scrubTemplateExpressions2 = function _scrubTemplateExpressions(node) {
30206
30291
  var _node$querySelectorAl;
30292
+ _scrubOwnCharacterData(node);
30293
+ const templates = (_node$querySelectorAl = node.querySelectorAll) === null || _node$querySelectorAl === void 0 ? void 0 : _node$querySelectorAl.call(node, "template");
30294
+ if (templates) arrayForEach(templates, (tmpl) => {
30295
+ if (_isDocumentFragment(tmpl.content)) _scrubTemplateExpressions2(tmpl.content);
30296
+ });
30297
+ };
30298
+ /**
30299
+ * _scrubOwnCharacterData
30300
+ *
30301
+ * Non-recursive core of _scrubTemplateExpressions: normalize `node` and
30302
+ * strip template expressions from its own character data only. Does not
30303
+ * enter <template>.content or attached shadow roots (NodeIterator never
30304
+ * does). Used by _sanitizeShadowDOM, where every nested template content
30305
+ * and shadow root is already walked - and therefore scrubbed - by its own
30306
+ * recursive _sanitizeShadowDOM call; recursing here as well would rescrub
30307
+ * each nested fragment once per ancestor, which is quadratic in nesting
30308
+ * depth.
30309
+ *
30310
+ * @param node The root whose own character data should be scrubbed.
30311
+ */
30312
+ const _scrubOwnCharacterData = function _scrubOwnCharacterData(node) {
30207
30313
  node.normalize();
30208
30314
  const doc = getOwnerDocument ? getOwnerDocument(node) : node.ownerDocument;
30209
30315
  const walker = createNodeIterator.call(doc || node, node, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
@@ -30212,10 +30318,6 @@ function createDOMPurify() {
30212
30318
  currentNode.data = _stripTemplateExpressions(currentNode.data);
30213
30319
  currentNode = walker.nextNode();
30214
30320
  }
30215
- const templates = (_node$querySelectorAl = node.querySelectorAll) === null || _node$querySelectorAl === void 0 ? void 0 : _node$querySelectorAl.call(node, "template");
30216
- if (templates) arrayForEach(templates, (tmpl) => {
30217
- if (_isDocumentFragment(tmpl.content)) _scrubTemplateExpressions2(tmpl.content);
30218
- });
30219
30321
  };
30220
30322
  /**
30221
30323
  * _isClobbered
@@ -30291,7 +30393,7 @@ function createDOMPurify() {
30291
30393
  */
30292
30394
  const _isUnsafeNode = function _isUnsafeNode(currentNode, tagName) {
30293
30395
  if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.innerHTML)) return true;
30294
- 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;
30396
+ if (SAFE_FOR_XML && LITERAL_TEXT_ELEMENTS[tagName] && _readNodeType(currentNode) === NODE_TYPE.element && _readNamespaceURI(currentNode) === HTML_NAMESPACE && (_isNode(currentNode.firstElementChild) || typeof currentNode.textContent === "string" && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent))) return true;
30295
30397
  if (currentNode.nodeType === NODE_TYPE.processingInstruction) return true;
30296
30398
  if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, currentNode.data)) return true;
30297
30399
  return false;
@@ -30335,7 +30437,7 @@ function createDOMPurify() {
30335
30437
  */
30336
30438
  const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName, root) {
30337
30439
  if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName) && _matchesNameCheck(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
30338
- if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
30440
+ if (KEEP_CONTENT && !FORBID_CONTENTS[tagName] && !FORBID_CONTENTS[stringToLowerCase(tagName)]) {
30339
30441
  const parentNode = getParentNode(currentNode);
30340
30442
  const childNodes = getChildNodes(currentNode);
30341
30443
  if (childNodes && parentNode) {
@@ -30368,35 +30470,13 @@ function createDOMPurify() {
30368
30470
  return set === defaultSet || set === setConfigSet ? clone(set) : set;
30369
30471
  };
30370
30472
  /**
30371
- * Shared guard for a node that a hook has detached from the walk tree,
30372
- * used after each element-hook site in _sanitizeElements. Detaching is a
30373
- * long-standing user pattern (issue #469; draw.io-style foreignObject
30374
- * filtering). Per the cached, unclobberable parentNode getter the node is
30375
- * genuinely out of the tree, so it can reach neither the serialized
30376
- * output nor an IN_PLACE live tree; treat it as removed and stop
30377
- * processing it. Without this guard, the unsafe-node / namespace checks
30378
- * would call _forceRemove on a parentless node and hit the REPORT-3
30379
- * fail-closed throw — which exists for nodes DOMPurify wants gone but
30380
- * *cannot* detach (clobbered / parentless roots), the opposite of a node
30381
- * that is already safely gone. The walk root is exempt: a detached
30382
- * IN_PLACE root is legitimate input and must still be fully sanitized,
30383
- * and a kill-decision on it must keep hitting the REPORT-3 throw.
30384
- *
30385
- * Nodes detached by hooks stay the hook's responsibility for placement:
30386
- * they are not recorded in DOMPurify.removed, so the post-walk IN_PLACE
30387
- * pass (which iterates DOMPurify.removed) does not reach them. But a
30388
- * hook-detached subtree can still hold a queued resource-event handler -
30389
- * e.g. an <img onload> that began loading when the caller built the live
30390
- * tree - which fires in page scope after sanitize returns even though the
30391
- * handler never reached the returned tree. That is the audit-5 F1 hazard,
30392
- * and the documented node.remove() hook pattern walks straight into it.
30393
- * So on the IN_PLACE path we neutralize the detached subtree inline,
30394
- * stripping its non-allow-listed attributes before returning, exactly as
30395
- * the post-walk pass does for _forceRemove'd subtrees.
30473
+ * A hook may have detached the node it was called for. If so, treat it as
30474
+ * removed and stop processing it; the walk root is exempt. On the IN_PLACE
30475
+ * path the detached subtree is neutralized right away.
30396
30476
  *
30397
30477
  * @param currentNode the node a hook may have detached
30398
30478
  * @param root the current walk root
30399
- * @return true if the node is detached and now handled, false otherwise
30479
+ * @return true if the node was detached
30400
30480
  */
30401
30481
  const _handleHookDetachedNode = function _handleHookDetachedNode(currentNode, root) {
30402
30482
  if (currentNode === root || getParentNode(currentNode) !== null) return false;
@@ -30413,6 +30493,7 @@ function createDOMPurify() {
30413
30493
  * @return true if node was killed, false if left alive
30414
30494
  */
30415
30495
  const _sanitizeElements = function _sanitizeElements(currentNode, root) {
30496
+ _markVisited(currentNode);
30416
30497
  _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
30417
30498
  if (_handleHookDetachedNode(currentNode, root)) return true;
30418
30499
  if (_isClobbered(currentNode)) {
@@ -30556,6 +30637,22 @@ function createDOMPurify() {
30556
30637
  }
30557
30638
  };
30558
30639
  /**
30640
+ * _isUnsafeAttributeValue
30641
+ *
30642
+ * Value-based attribute checks that the attribute walk applies before any
30643
+ * hook can force-keep an attribute, shared with the public
30644
+ * isValidAttribute() so that it never approves a value sanitize() would
30645
+ * strip.
30646
+ *
30647
+ * @param name the attribute name as found on the node (case preserved)
30648
+ * @param value the attribute value
30649
+ * @returns true if the attribute must be removed because of its value
30650
+ */
30651
+ const _isUnsafeAttributeValue = function _isUnsafeAttributeValue(name, value) {
30652
+ if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) return true;
30653
+ return stringToLowerCase(name) === "attributename" && stringMatch(value, "href") !== null;
30654
+ };
30655
+ /**
30559
30656
  * _sanitizeAttributes
30560
30657
  *
30561
30658
  * @protect attributes
@@ -30599,11 +30696,7 @@ function createDOMPurify() {
30599
30696
  value = SANITIZE_NAMED_PROPS_PREFIX + value;
30600
30697
  recreatedNamedProp = true;
30601
30698
  }
30602
- if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
30603
- _removeAttribute(name, currentNode, attr);
30604
- continue;
30605
- }
30606
- if (lcName === "attributename" && stringMatch(value, "href")) {
30699
+ if (_isUnsafeAttributeValue(name, value)) {
30607
30700
  _removeAttribute(name, currentNode, attr);
30608
30701
  continue;
30609
30702
  }
@@ -30651,24 +30744,13 @@ function createDOMPurify() {
30651
30744
  }
30652
30745
  }
30653
30746
  }
30747
+ if (SAFE_FOR_TEMPLATES) _scrubOwnCharacterData(fragment);
30654
30748
  _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
30655
30749
  };
30656
30750
  /**
30657
- * _sanitizeAttachedShadowRoots
30658
- *
30659
- * Walks `root` and feeds every attached shadow root we encounter into
30660
- * the existing _sanitizeShadowDOM pipeline. The default node iterator
30661
- * does not descend into shadow trees, so nodes inside an attached
30662
- * shadow root would otherwise be skipped entirely.
30663
- *
30664
- * Two real input paths put attached shadow roots in front of us:
30665
- * 1. IN_PLACE on a DOM node that already has shadow roots attached.
30666
- * 2. DOM-node input where importNode(dirty, true) deep-clones the
30667
- * shadow root because it was created with `clonable: true`.
30668
- *
30669
- * This pass runs once, up front, so the main iteration loop (and the
30670
- * existing _sanitizeShadowDOM template-content recursion) stay
30671
- * untouched — string-input paths are not affected.
30751
+ * Sanitizes every attached shadow root under `root`, since the node
30752
+ * iterator does not enter shadow trees. Needed for IN_PLACE input and for
30753
+ * DOM input whose clonable shadow roots were copied by importNode().
30672
30754
  *
30673
30755
  * @param root the subtree root to walk for attached shadow roots
30674
30756
  */
@@ -30733,6 +30815,8 @@ function createDOMPurify() {
30733
30815
  if (hooks.uponSanitizeAttribute.length > 0) ALLOWED_ATTR = clone(ALLOWED_ATTR);
30734
30816
  DOMPurify.removed = [];
30735
30817
  const inPlace = IN_PLACE && typeof dirty !== "string" && _isNode(dirty);
30818
+ let inPlaceNodes = [];
30819
+ const outerVisited = IN_PLACE_VISITED;
30736
30820
  if (inPlace) {
30737
30821
  _neutralizePatchLinkage(dirty);
30738
30822
  const nn = _readNodeName(dirty);
@@ -30747,12 +30831,8 @@ function createDOMPurify() {
30747
30831
  _neutralizeRoot(dirty);
30748
30832
  throw typeErrorCreate("root node is clobbered and cannot be sanitized in-place");
30749
30833
  }
30750
- try {
30751
- _sanitizeAttachedShadowRoots(dirty);
30752
- } catch (error) {
30753
- _neutralizeRoot(dirty);
30754
- throw error;
30755
- }
30834
+ inPlaceNodes = _collectLiveNodes(dirty);
30835
+ IN_PLACE_VISITED = setCreate();
30756
30836
  } else if (_isNode(dirty)) {
30757
30837
  body = _initDocument("<!---->");
30758
30838
  importedNode = body.ownerDocument.importNode(dirty, true);
@@ -30768,30 +30848,25 @@ function createDOMPurify() {
30768
30848
  if (body && FORCE_BODY) _forceRemove(body.firstChild);
30769
30849
  const walkRoot = inPlace ? dirty : body;
30770
30850
  try {
30851
+ if (inPlace) _sanitizeAttachedShadowRoots(dirty);
30771
30852
  const nodeIterator = _createNodeIterator(walkRoot);
30772
30853
  while (currentNode = nodeIterator.nextNode()) {
30773
30854
  _sanitizeElements(currentNode, walkRoot);
30774
30855
  _sanitizeAttributes(currentNode, walkRoot);
30775
30856
  if (_isDocumentFragment(currentNode.content)) _sanitizeShadowDOM2(currentNode.content);
30776
30857
  }
30858
+ if (inPlace) _sanitizeUnvisitedNodes(dirty, inPlaceNodes);
30777
30859
  } catch (error) {
30778
30860
  if (inPlace) {
30779
30861
  _neutralizeRoot(dirty);
30780
- arrayForEach(DOMPurify.removed, (entry) => {
30781
- if (entry.element) _neutralizeSubtree(entry.element);
30782
- });
30862
+ _neutralizeDiscarded(dirty, inPlaceNodes);
30783
30863
  }
30784
30864
  throw error;
30865
+ } finally {
30866
+ IN_PLACE_VISITED = outerVisited;
30785
30867
  }
30786
30868
  if (inPlace) {
30787
- let rootWasRemoved = false;
30788
- arrayForEach(DOMPurify.removed, (entry) => {
30789
- if (entry.element) {
30790
- if (entry.element === dirty) rootWasRemoved = true;
30791
- _neutralizeSubtree(entry.element);
30792
- }
30793
- });
30794
- if (rootWasRemoved) throw typeErrorCreate("a node selected for removal could not be safely returned; refusing to sanitize in place");
30869
+ if (_neutralizeDiscarded(dirty, inPlaceNodes)) throw typeErrorCreate("a node selected for removal could not be safely returned; refusing to sanitize in place");
30795
30870
  if (SAFE_FOR_TEMPLATES) _scrubTemplateExpressions2(dirty);
30796
30871
  return dirty;
30797
30872
  }
@@ -30828,6 +30903,7 @@ function createDOMPurify() {
30828
30903
  if (!CONFIG) _parseConfig({});
30829
30904
  const lcTag = transformCaseFunc(tag);
30830
30905
  const lcName = transformCaseFunc(attr);
30906
+ if (typeof value === "string" && (_isUnsafeAttributeValue(attr, value) || !ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value))) return false;
30831
30907
  return _isValidAttribute(lcTag, lcName, value);
30832
30908
  };
30833
30909
  DOMPurify.addHook = function(entryPoint, hookFunction) {
@@ -57189,7 +57265,7 @@ var InkKitEditor = class InkKitEditor {
57189
57265
  return true;
57190
57266
  }
57191
57267
  }));
57192
- }).use(labelsCtx).use(instance.pasteController.plugin).use(visibleClipboard).use($prose(() => instance.suggestions.plugin())).use(instance.keymapPlugin).use(createDialect(Boolean(options.images || options.files), Boolean(options.wikiLinks), Boolean(options.files))).use(codeControlCtx).use(codeControlsPlugin(() => instance.viewport().rect)).use(tablePlugins).use(tableBlock).use(instance.changePlugin).use(instance.historyInputPlugin).use(history).use(clipboard).use(cursor).use(taskListPlugin).use(quoteBackspace).use(calloutEnter).use(literalEnter).use(literalReread).use(listItemBackspace).use(listItemShiftTab).use(nestedItemEnter).use(rejoinLists).use(movedListItems).use(textInputPolicy).use(events.copy ? codeCopyPlugin((text) => events.copy?.(text)) : []).use(placeholderPlugin).use(options.rendering?.codeHighlighting === false ? [] : (await import("./highlight-BIasnL-b.js")).highlightPlugin).use(options.rendering?.diagramPreview === false || !diagramsAvailable() ? [] : mermaidPreview((error) => instance.report(error))).use(selectionPlugin).use($prose(() => search())).use(instance.policyPlugin).use(options.wikiLinks ? wikiLinkView(options.wikiLinks, {
57268
+ }).use(labelsCtx).use(instance.pasteController.plugin).use(visibleClipboard).use($prose(() => instance.suggestions.plugin())).use(instance.keymapPlugin).use(createDialect(Boolean(options.images || options.files), Boolean(options.wikiLinks), Boolean(options.files))).use(codeControlCtx).use(codeControlsPlugin(() => instance.viewport().rect)).use(tablePlugins).use(tableBlock).use(instance.changePlugin).use(instance.historyInputPlugin).use(history).use(clipboard).use(cursor).use(taskListPlugin).use(quoteBackspace).use(calloutEnter).use(literalEnter).use(literalReread).use(listItemBackspace).use(listItemShiftTab).use(nestedItemEnter).use(rejoinLists).use(movedListItems).use(textInputPolicy).use(events.copy ? codeCopyPlugin((text) => events.copy?.(text)) : []).use(placeholderPlugin).use(options.rendering?.codeHighlighting === false ? [] : (await import("./highlight-CeNqcHAb.js")).highlightPlugin).use(options.rendering?.diagramPreview === false || !diagramsAvailable() ? [] : mermaidPreview((error) => instance.report(error))).use(selectionPlugin).use($prose(() => search())).use(instance.policyPlugin).use(options.wikiLinks ? wikiLinkView(options.wikiLinks, {
57193
57269
  capture: () => instance.adapterCapture(),
57194
57270
  active: () => instance.ready && !instance.destroyed && !instance.literalSurface,
57195
57271
  labels: {
@@ -1,4 +1,4 @@
1
- import { a as Decoration, c as PluginKey, d as __toESM, i as $prose, o as DecorationSet, s as Plugin, u as __commonJSMin } from "./core-D7n0IhAZ.js";
1
+ import { a as Decoration, c as PluginKey, d as __toESM, i as $prose, o as DecorationSet, s as Plugin, u as __commonJSMin } from "./core-nIObsA9D.js";
2
2
  //#region node_modules/.pnpm/highlight.js@11.11.2/node_modules/highlight.js/es/languages/arduino.js
3
3
  /** @type LanguageFn */
4
4
  function cPlusPlus(hljs) {
package/dist/core.js CHANGED
@@ -1,2 +1,2 @@
1
- import { r as InkKitError, t as InkKitEditor } from "./chunks/core-D7n0IhAZ.js";
1
+ import { r as InkKitError, t as InkKitEditor } from "./chunks/core-nIObsA9D.js";
2
2
  export { InkKitEditor, InkKitError };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as registerMermaid, r as InkKitError, t as InkKitEditor } from "./chunks/core-D7n0IhAZ.js";
1
+ import { n as registerMermaid, r as InkKitError, t as InkKitEditor } from "./chunks/core-nIObsA9D.js";
2
2
  //#region src/index.ts
3
3
  registerMermaid(async () => (await import("mermaid")).default);
4
4
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aicayzer/inkkit",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "A preserving Markdown editor for offline web views and the web",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -64,7 +64,7 @@
64
64
  "@types/mdast": "^4.0.4",
65
65
  "@types/node": "^26.6.2",
66
66
  "axe-core": "^4.14.0",
67
- "dompurify": "3.4.16",
67
+ "dompurify": "3.4.17",
68
68
  "jsdom": "^30.1.0",
69
69
  "lowlight": "^3.3.0",
70
70
  "mdast-util-from-markdown": "^2.1.0",