tinymce-rails 8.8.0 → 8.8.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.
- checksums.yaml +4 -4
- data/app/assets/source/tinymce/tinymce.js +254 -54
- data/lib/tinymce/rails/version.rb +2 -2
- data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.css +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.js +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.min.css +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.css +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.js +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.min.css +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.css +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.js +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.min.css +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.css +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.js +1 -1
- data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.min.css +1 -1
- data/vendor/assets/javascripts/tinymce/themes/silver/theme.js +3 -3
- data/vendor/assets/javascripts/tinymce/tinymce.js +4 -4
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0c76539b0f4c153f018da0a4c42acce9a1dcea31efb03dabc9f44d1e1d61b3ec
|
|
4
|
+
data.tar.gz: b0ec30ffc6de0ffa5fcd0720388ef73f7ca5af1c966baac68e911c9699a59a29
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3229b680d9cb17075ef7686f230ebc0e5bc3ae090e4623c28c025ecd98c96cc9fa537d1a35010baceb03d365ae6590babaad2dce34f3f6ee53e0a25d47b6d94f
|
|
7
|
+
data.tar.gz: 03ac9f068a069403e8c8d3df13172a213794fd1cba3d63a1d703372919bd9873a80f2bf1180587dac4f1943e7c290fc182e71f69fa88e5b9c4f718284b506323
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TinyMCE version 8.8.
|
|
2
|
+
* TinyMCE version 8.8.2 (2026-07-27)
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
(function () {
|
|
@@ -12153,6 +12153,13 @@
|
|
|
12153
12153
|
const selector = isEmpty$5(formatNoneditableSelector) ? baseDataSelector : `${baseDataSelector},${formatNoneditableSelector}`;
|
|
12154
12154
|
return is$2(SugarElement.fromDom(node), selector);
|
|
12155
12155
|
};
|
|
12156
|
+
const hasEditableDescendants = (dom, node) => dom.select('[contenteditable="true"]', node).length > 0;
|
|
12157
|
+
const getEditableDescendants = (dom, parent) => foldl(from(parent.childNodes), (editableDescendants, child) => [
|
|
12158
|
+
...editableDescendants,
|
|
12159
|
+
...(isElement$7(child) && dom.getContentEditable(child) === 'true'
|
|
12160
|
+
? [child]
|
|
12161
|
+
: getEditableDescendants(dom, child))
|
|
12162
|
+
], []);
|
|
12156
12163
|
// A noneditable element is wrappable if it:
|
|
12157
12164
|
// - is valid target (has data-mce-cef-wrappable attribute or matches selector from option)
|
|
12158
12165
|
// - has no editable descendants - removing formats in the editable region can result in the wrapped noneditable being split which is undesirable
|
|
@@ -12161,7 +12168,7 @@
|
|
|
12161
12168
|
return (isElementNode$1(node) &&
|
|
12162
12169
|
dom.getContentEditable(node) === 'false' &&
|
|
12163
12170
|
isWrapNoneditableTarget(editor, node) &&
|
|
12164
|
-
dom
|
|
12171
|
+
!hasEditableDescendants(dom, node));
|
|
12165
12172
|
};
|
|
12166
12173
|
/**
|
|
12167
12174
|
* Replaces variables in the value. The variable format is %var.
|
|
@@ -16769,14 +16776,23 @@
|
|
|
16769
16776
|
}
|
|
16770
16777
|
return undefined;
|
|
16771
16778
|
};
|
|
16779
|
+
const containsFormatDeep = (editor, root, name, vars, similar) => isNonNullable(matchNode$1(editor, root, name, vars, similar)) ||
|
|
16780
|
+
exists(from(root.childNodes), (child) => containsFormatDeep(editor, child, name, vars, similar));
|
|
16781
|
+
const matchEditableDescendants = (editor, node, name, vars, similar) => {
|
|
16782
|
+
const dom = editor.dom;
|
|
16783
|
+
if (!isElement$7(node) || dom.getContentEditable(node) !== 'false' || isWrappableNoneditable(editor, node)) {
|
|
16784
|
+
return false;
|
|
16785
|
+
}
|
|
16786
|
+
return exists(getEditableDescendants(dom, node), (descendant) => containsFormatDeep(editor, descendant, name, vars, similar));
|
|
16787
|
+
};
|
|
16772
16788
|
const match$2 = (editor, name, vars, node, similar) => {
|
|
16773
16789
|
// Check specified node
|
|
16774
16790
|
if (node) {
|
|
16775
|
-
return matchParents(editor, node, name, vars, similar);
|
|
16791
|
+
return matchParents(editor, node, name, vars, similar) || matchEditableDescendants(editor, node, name, vars, similar);
|
|
16776
16792
|
}
|
|
16777
16793
|
// Check selected node
|
|
16778
16794
|
node = editor.selection.getNode();
|
|
16779
|
-
if (matchParents(editor, node, name, vars, similar)) {
|
|
16795
|
+
if (matchParents(editor, node, name, vars, similar) || matchEditableDescendants(editor, node, name, vars, similar)) {
|
|
16780
16796
|
return true;
|
|
16781
16797
|
}
|
|
16782
16798
|
// Check start node if it's different
|
|
@@ -17181,6 +17197,18 @@
|
|
|
17181
17197
|
return block;
|
|
17182
17198
|
}
|
|
17183
17199
|
});
|
|
17200
|
+
// Resolve list items from the
|
|
17201
|
+
// selected cells directly instead, matching how SelectionUtils.runOnRanges resolves fake selections.
|
|
17202
|
+
const getCellSelectionListItems = (editor) => {
|
|
17203
|
+
const fakeSelectionNodes = getCellsFromEditor(editor);
|
|
17204
|
+
if (fakeSelectionNodes.length > 0) {
|
|
17205
|
+
const listItems = bind$3(fakeSelectionNodes, (cell) => descendants(cell, 'li'));
|
|
17206
|
+
return Optional.some(map$3(listItems, (item) => item.dom));
|
|
17207
|
+
}
|
|
17208
|
+
else {
|
|
17209
|
+
return Optional.none();
|
|
17210
|
+
}
|
|
17211
|
+
};
|
|
17184
17212
|
const getFullySelectedBlocks = (selection) => {
|
|
17185
17213
|
if (selection.isCollapsed()) {
|
|
17186
17214
|
return [];
|
|
@@ -17197,8 +17225,15 @@
|
|
|
17197
17225
|
return first.concat(middle).concat(last);
|
|
17198
17226
|
}
|
|
17199
17227
|
};
|
|
17200
|
-
const getFullySelectedListItems = (
|
|
17201
|
-
|
|
17228
|
+
const getFullySelectedListItems = (editor) => {
|
|
17229
|
+
const items = getCellSelectionListItems(editor).getOrThunk(() => getFullySelectedBlocks(editor.selection));
|
|
17230
|
+
return filter$5(items, isEditableListItem(editor.selection.dom));
|
|
17231
|
+
};
|
|
17232
|
+
const getPartiallySelectedListItems = (editor) => {
|
|
17233
|
+
const items = getCellSelectionListItems(editor)
|
|
17234
|
+
.getOrThunk(() => getAndOnlyNormalizeFirstBlockIf(editor.selection, (el) => !isListItem$3(el)));
|
|
17235
|
+
return filter$5(items, isEditableListItem(editor.selection.dom));
|
|
17236
|
+
};
|
|
17202
17237
|
|
|
17203
17238
|
const each$8 = Tools.each;
|
|
17204
17239
|
const isElementNode = (node) => isElement$7(node) && !isBookmarkNode$1(node) && !isCaretNode(node) && !isBogus$1(node);
|
|
@@ -17440,14 +17475,14 @@
|
|
|
17440
17475
|
};
|
|
17441
17476
|
const removeListStyleFormats = (editor, name, vars) => {
|
|
17442
17477
|
if (name === 'removeformat') {
|
|
17443
|
-
each$e(getPartiallySelectedListItems(editor
|
|
17478
|
+
each$e(getPartiallySelectedListItems(editor), (li) => {
|
|
17444
17479
|
each$e(listItemStyles, (name) => editor.dom.setStyle(li, name, ''));
|
|
17445
17480
|
removeEmptyStyleAttributeIfNeeded(editor.dom, li);
|
|
17446
17481
|
});
|
|
17447
17482
|
}
|
|
17448
17483
|
else {
|
|
17449
17484
|
getExpandedListItemFormat(editor.formatter, name).each((liFmt) => {
|
|
17450
|
-
each$e(getPartiallySelectedListItems(editor
|
|
17485
|
+
each$e(getPartiallySelectedListItems(editor), (li) => removeStyles$1(editor.dom, li, liFmt, vars, null));
|
|
17451
17486
|
});
|
|
17452
17487
|
}
|
|
17453
17488
|
};
|
|
@@ -18628,7 +18663,7 @@
|
|
|
18628
18663
|
}
|
|
18629
18664
|
};
|
|
18630
18665
|
|
|
18631
|
-
/*! @license DOMPurify 3.4.
|
|
18666
|
+
/*! @license DOMPurify 3.4.12 | (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.12/LICENSE */
|
|
18632
18667
|
|
|
18633
18668
|
function _arrayLikeToArray(r, a) {
|
|
18634
18669
|
(null == a || a > r.length) && (a = r.length);
|
|
@@ -18941,7 +18976,7 @@
|
|
|
18941
18976
|
const text = freeze(['#text']);
|
|
18942
18977
|
|
|
18943
18978
|
const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'command', 'commandfor', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns']);
|
|
18944
|
-
const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
|
|
18979
|
+
const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dominant-baseline', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-orientation', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
|
|
18945
18980
|
const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnalign', 'columnlines', 'columnspacing', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lquote', 'lspace', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
|
|
18946
18981
|
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
18947
18982
|
|
|
@@ -19054,7 +19089,7 @@
|
|
|
19054
19089
|
function createDOMPurify() {
|
|
19055
19090
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
19056
19091
|
const DOMPurify = root => createDOMPurify(root);
|
|
19057
|
-
DOMPurify.version = '3.4.
|
|
19092
|
+
DOMPurify.version = '3.4.12';
|
|
19058
19093
|
DOMPurify.removed = [];
|
|
19059
19094
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
19060
19095
|
// Not running in a browser, provide a factory function
|
|
@@ -19736,6 +19771,13 @@
|
|
|
19736
19771
|
* @param root the in-place root to empty
|
|
19737
19772
|
*/
|
|
19738
19773
|
const _neutralizeRoot = function _neutralizeRoot(root) {
|
|
19774
|
+
/* Strip every disallowed attribute (on* handlers included) off the whole
|
|
19775
|
+
subtree BEFORE detaching anything. Detaching first would hand back
|
|
19776
|
+
handler-bearing originals (e.g. an already-loading `<img onerror>`)
|
|
19777
|
+
whose queued resource event still fires in page scope after we throw.
|
|
19778
|
+
Clobber-safe reads; a doomed clobbered node's own attributes are
|
|
19779
|
+
irrelevant while its non-clobbered descendants are reached and scrubbed. */
|
|
19780
|
+
_neutralizeSubtree(root);
|
|
19739
19781
|
const childNodes = getChildNodes(root);
|
|
19740
19782
|
if (childNodes) {
|
|
19741
19783
|
const snapshot = [];
|
|
@@ -19863,6 +19905,82 @@
|
|
|
19863
19905
|
}
|
|
19864
19906
|
}
|
|
19865
19907
|
};
|
|
19908
|
+
/**
|
|
19909
|
+
* _neutralizePatchLinkage
|
|
19910
|
+
*
|
|
19911
|
+
* IN_PLACE entry pre-pass (declarative-partial-updates / streaming
|
|
19912
|
+
* hardening, https://github.com/WICG/declarative-partial-updates).
|
|
19913
|
+
*
|
|
19914
|
+
* The main walk strips patch linkage (`for`/`patchsrc`) and removes range
|
|
19915
|
+
* markers (PIs / markup comments) node-by-node, in document order, AS it
|
|
19916
|
+
* reaches each node. On a live in-place root that leaves a window: from the
|
|
19917
|
+
* moment the root is connected until the walk arrives at a given node, that
|
|
19918
|
+
* node's linkage is live. A patch applied on connection/stream can fire as
|
|
19919
|
+
* a microtask during the walk and inject or teleport an unsanitized DOM
|
|
19920
|
+
* range into a region the iterator has already passed and will not revisit,
|
|
19921
|
+
* so the post-return "tree is sanitized" contract is violated. Sweep the
|
|
19922
|
+
* whole tree once up front and sever every linkage before the walk begins,
|
|
19923
|
+
* closing that window.
|
|
19924
|
+
*
|
|
19925
|
+
* This CANNOT undo a patch that already fired before sanitize ran — that is
|
|
19926
|
+
* the irreducible "do not IN_PLACE a live-connected attacker tree" caveat —
|
|
19927
|
+
* but it closes everything from sanitize-start onward. Gated on SAFE_FOR_XML
|
|
19928
|
+
* to group with the rest of the declarative-partial-updates handling and
|
|
19929
|
+
* stay overridable, consistent with the codebase.
|
|
19930
|
+
*
|
|
19931
|
+
* Clobber-safe traversal (cached childNodes getter); per-node try/catch so a
|
|
19932
|
+
* clobbered root cannot defeat the sweep of its non-clobbered descendants.
|
|
19933
|
+
*
|
|
19934
|
+
* NOTE (pending real-Chrome confirmation, see test/declarative-patch-probe
|
|
19935
|
+
* .html Q1): this mirrors the existing policy of keeping `for` on
|
|
19936
|
+
* <label>/<output>. If the shipping feature can drive a patch through a
|
|
19937
|
+
* surviving `for`-on-label/output + `id` pair, this pre-pass and the
|
|
19938
|
+
* attribute check at _isBasicCustomElement's caller must additionally drop
|
|
19939
|
+
* that pair on the IN_PLACE path. Left as-is until the taxonomy is verified.
|
|
19940
|
+
*
|
|
19941
|
+
* @param root the in-place root to sweep
|
|
19942
|
+
*/
|
|
19943
|
+
const _neutralizePatchLinkage = function _neutralizePatchLinkage(root) {
|
|
19944
|
+
if (!SAFE_FOR_XML) {
|
|
19945
|
+
return;
|
|
19946
|
+
}
|
|
19947
|
+
const stack = [root];
|
|
19948
|
+
while (stack.length > 0) {
|
|
19949
|
+
const node = stack.pop();
|
|
19950
|
+
const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
|
|
19951
|
+
/* Remove range markers (the target side of a patch linkage): every
|
|
19952
|
+
processing instruction, and any markup-bearing comment. */
|
|
19953
|
+
if (nodeType === NODE_TYPE.processingInstruction || nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, node.data)) {
|
|
19954
|
+
try {
|
|
19955
|
+
remove(node);
|
|
19956
|
+
} catch (_) {
|
|
19957
|
+
/* Best-effort */
|
|
19958
|
+
}
|
|
19959
|
+
continue;
|
|
19960
|
+
}
|
|
19961
|
+
/* Strip patch-source attributes (the source side) off elements. */
|
|
19962
|
+
if (nodeType === NODE_TYPE.element) {
|
|
19963
|
+
const element = node;
|
|
19964
|
+
const lcTag = transformCaseFunc(getNodeName ? getNodeName(node) : node.nodeName);
|
|
19965
|
+
try {
|
|
19966
|
+
if (element.hasAttribute && element.hasAttribute('patchsrc')) {
|
|
19967
|
+
element.removeAttribute('patchsrc');
|
|
19968
|
+
}
|
|
19969
|
+
if (element.hasAttribute && element.hasAttribute('for') && lcTag !== 'label' && lcTag !== 'output') {
|
|
19970
|
+
element.removeAttribute('for');
|
|
19971
|
+
}
|
|
19972
|
+
} catch (_) {
|
|
19973
|
+
/* Clobbered removeAttribute/hasAttribute on a doomed node — ignore */
|
|
19974
|
+
}
|
|
19975
|
+
}
|
|
19976
|
+
const childNodes = getChildNodes(node);
|
|
19977
|
+
if (childNodes) {
|
|
19978
|
+
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
19979
|
+
stack.push(childNodes[i]);
|
|
19980
|
+
}
|
|
19981
|
+
}
|
|
19982
|
+
}
|
|
19983
|
+
};
|
|
19866
19984
|
/**
|
|
19867
19985
|
* _initDocument
|
|
19868
19986
|
*
|
|
@@ -20113,9 +20231,15 @@
|
|
|
20113
20231
|
/**
|
|
20114
20232
|
* Handle a node whose tag is forbidden or not allowlisted: keep
|
|
20115
20233
|
* allowed custom elements (false return exits _sanitizeElements
|
|
20116
|
-
* early - namespace
|
|
20117
|
-
*
|
|
20118
|
-
*
|
|
20234
|
+
* early - the namespace and fallback-tag removal checks are
|
|
20235
|
+
* intentionally skipped for kept custom elements), else hoist
|
|
20236
|
+
* content per KEEP_CONTENT and remove.
|
|
20237
|
+
*
|
|
20238
|
+
* A kept custom element is the ONLY case in which this function
|
|
20239
|
+
* returns false, so the caller uses that return value to run the
|
|
20240
|
+
* afterSanitizeElements hook on the kept element and keep the
|
|
20241
|
+
* element-hook lifecycle consistent with normal allowlisted
|
|
20242
|
+
* elements (GHSA-c2j3-45gr-mqc4).
|
|
20119
20243
|
*
|
|
20120
20244
|
* @param currentNode the disallowed node
|
|
20121
20245
|
* @param tagName the node's transformCaseFunc'd tag name
|
|
@@ -20181,9 +20305,15 @@
|
|
|
20181
20305
|
* @param currentNode to check for permission to exist
|
|
20182
20306
|
* @return true if node was killed, false if left alive
|
|
20183
20307
|
*/
|
|
20184
|
-
|
|
20308
|
+
// eslint-disable-next-line complexity
|
|
20309
|
+
const _sanitizeElements = function _sanitizeElements(currentNode, root) {
|
|
20185
20310
|
/* Execute a hook if present */
|
|
20186
20311
|
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
20312
|
+
/* A hook may have detached the node — treat it as removed (see the
|
|
20313
|
+
detached-node comment after the uponSanitizeElement hook below). */
|
|
20314
|
+
if (currentNode !== root && getParentNode(currentNode) === null) {
|
|
20315
|
+
return true;
|
|
20316
|
+
}
|
|
20187
20317
|
/* Check if element is clobbered or can clobber */
|
|
20188
20318
|
if (_isClobbered(currentNode)) {
|
|
20189
20319
|
_forceRemove(currentNode);
|
|
@@ -20196,6 +20326,24 @@
|
|
|
20196
20326
|
tagName,
|
|
20197
20327
|
allowedTags: ALLOWED_TAGS
|
|
20198
20328
|
});
|
|
20329
|
+
/* A hook may have detached the node from the tree — a long-standing
|
|
20330
|
+
user pattern (issue #469; draw.io-style foreignObject filtering).
|
|
20331
|
+
Per the cached, unclobberable parentNode getter the node is
|
|
20332
|
+
genuinely out of the tree, so it can reach neither the serialized
|
|
20333
|
+
output nor an IN_PLACE live tree; treat it as removed and stop
|
|
20334
|
+
processing it. Without this guard, the unsafe-node / namespace
|
|
20335
|
+
checks below would call _forceRemove on a parentless node and hit
|
|
20336
|
+
the REPORT-3 fail-closed throw — which exists for nodes DOMPurify
|
|
20337
|
+
wants gone but *cannot* detach (clobbered / parentless roots), the
|
|
20338
|
+
opposite of a node that is already safely gone. The walk root is
|
|
20339
|
+
exempt: a detached IN_PLACE root is legitimate input and must still
|
|
20340
|
+
be fully sanitized, and a kill-decision on it must keep hitting the
|
|
20341
|
+
REPORT-3 throw. Nodes detached by hooks are the hook's
|
|
20342
|
+
responsibility: they are not recorded in DOMPurify.removed and are
|
|
20343
|
+
not neutralized by the post-walk IN_PLACE pass. */
|
|
20344
|
+
if (currentNode !== root && getParentNode(currentNode) === null) {
|
|
20345
|
+
return true;
|
|
20346
|
+
}
|
|
20199
20347
|
/* Remove mXSS vectors, processing instructions and risky comments */
|
|
20200
20348
|
if (_isUnsafeNode(currentNode, tagName)) {
|
|
20201
20349
|
_forceRemove(currentNode);
|
|
@@ -20203,7 +20351,22 @@
|
|
|
20203
20351
|
}
|
|
20204
20352
|
/* Remove element if anything forbids its presence */
|
|
20205
20353
|
if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
|
|
20206
|
-
|
|
20354
|
+
const removed = _sanitizeDisallowedNode(currentNode, tagName);
|
|
20355
|
+
/* A false return means the node is a custom element kept via
|
|
20356
|
+
CUSTOM_ELEMENT_HANDLING - the only keep path through
|
|
20357
|
+
_sanitizeDisallowedNode. Run afterSanitizeElements on it so the
|
|
20358
|
+
element-hook lifecycle matches normal allowlisted elements: a
|
|
20359
|
+
security policy applied in this hook (e.g. stripping an attribute
|
|
20360
|
+
from every surviving element) must not silently skip kept custom
|
|
20361
|
+
elements (GHSA-c2j3-45gr-mqc4). This mirrors the normal-element
|
|
20362
|
+
tail below - the hook runs, then the walker's subsequent
|
|
20363
|
+
_sanitizeAttributes pass sanitizes the element's attributes. The
|
|
20364
|
+
deliberately skipped namespace and fallback-tag removal checks stay
|
|
20365
|
+
skipped; they are removal decisions, not the hook contract. */
|
|
20366
|
+
if (removed === false) {
|
|
20367
|
+
_executeHooks(hooks.afterSanitizeElements, currentNode, null);
|
|
20368
|
+
}
|
|
20369
|
+
return removed;
|
|
20207
20370
|
}
|
|
20208
20371
|
/* Check whether element has a valid namespace.
|
|
20209
20372
|
Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype
|
|
@@ -20250,6 +20413,33 @@
|
|
|
20250
20413
|
if (FORBID_ATTR[lcName]) {
|
|
20251
20414
|
return false;
|
|
20252
20415
|
}
|
|
20416
|
+
/* Reject declarative-partial-updates patch-linkage attributes
|
|
20417
|
+
(https://github.com/WICG/declarative-partial-updates).
|
|
20418
|
+
Empirical note (Chrome 150, verified — see
|
|
20419
|
+
test/declarative-patch-probe-v3.html): expansion is NOT applied after
|
|
20420
|
+
sanitization. For the string path it fires during sanitize()'s own
|
|
20421
|
+
parse, so the walk sees and sanitizes the fully materialized expanded
|
|
20422
|
+
tree — teleports into MathML/SVG integration points included; a
|
|
20423
|
+
weaponized `<template for>`->`<img onerror>` comes back with the handler
|
|
20424
|
+
stripped. For the IN_PLACE path it fires on connection, before the walk.
|
|
20425
|
+
Either way DOMPurify is NOT blind to the patch.
|
|
20426
|
+
This removal is therefore defense-in-depth rather than the sole barrier:
|
|
20427
|
+
it prevents live linkage from surviving into the OUTPUT and re-expanding
|
|
20428
|
+
in the caller's context, and keeps behaviour deterministic if a future
|
|
20429
|
+
engine defers expansion. `for` is legitimate only on <label>/<output>;
|
|
20430
|
+
anywhere else (notably <template for>) it links the element to a patch
|
|
20431
|
+
target and teleports or removes an arbitrary DOM range by id/marker name.
|
|
20432
|
+
`patchsrc` fetches remote markup and is treated as a script-loading
|
|
20433
|
+
mechanism (CSP). Gated on SAFE_FOR_XML so the removal groups with the
|
|
20434
|
+
other structural-threat checks and stays overridable, consistent with
|
|
20435
|
+
the rest of the codebase. PI range markers are already removed by
|
|
20436
|
+
_isUnsafeNode. */
|
|
20437
|
+
if (SAFE_FOR_XML && lcName === 'patchsrc') {
|
|
20438
|
+
return false;
|
|
20439
|
+
}
|
|
20440
|
+
if (SAFE_FOR_XML && lcName === 'for' && lcTag !== 'label' && lcTag !== 'output') {
|
|
20441
|
+
return false;
|
|
20442
|
+
}
|
|
20253
20443
|
/* Make sure attribute cannot clobber */
|
|
20254
20444
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
|
|
20255
20445
|
return false;
|
|
@@ -20459,7 +20649,7 @@
|
|
|
20459
20649
|
/* Execute a hook if present */
|
|
20460
20650
|
_executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
|
|
20461
20651
|
/* Sanitize tags and elements */
|
|
20462
|
-
_sanitizeElements(shadowNode);
|
|
20652
|
+
_sanitizeElements(shadowNode, fragment);
|
|
20463
20653
|
/* Check attributes next */
|
|
20464
20654
|
_sanitizeAttributes(shadowNode);
|
|
20465
20655
|
/* Deep shadow DOM detected.
|
|
@@ -20650,6 +20840,11 @@
|
|
|
20650
20840
|
keep using and whose return value they ignore — unsanitized. REPORT-2. */
|
|
20651
20841
|
const inPlace = IN_PLACE && typeof dirty !== 'string' && _isNode(dirty);
|
|
20652
20842
|
if (inPlace) {
|
|
20843
|
+
/* Declarative-partial-updates / streaming pre-pass: sever every patch
|
|
20844
|
+
linkage across the live tree BEFORE the walk, so no patch can fire
|
|
20845
|
+
mid-walk and inject into an already-processed region. Runs first, so
|
|
20846
|
+
it also covers the forbidden/clobbered roots that throw below. */
|
|
20847
|
+
_neutralizePatchLinkage(dirty);
|
|
20653
20848
|
/* Do some early pre-sanitization to avoid unsafe root nodes.
|
|
20654
20849
|
Read nodeName through the cached prototype getter — a clobbering
|
|
20655
20850
|
child named "nodeName" on the form root would otherwise shadow
|
|
@@ -20659,6 +20854,9 @@
|
|
|
20659
20854
|
if (typeof nn === 'string') {
|
|
20660
20855
|
const tagName = transformCaseFunc(nn);
|
|
20661
20856
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
20857
|
+
/* Fail closed on a live root: neutralize handlers/children before
|
|
20858
|
+
throwing, exactly as the mid-walk abort path does. */
|
|
20859
|
+
_neutralizeRoot(dirty);
|
|
20662
20860
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
|
|
20663
20861
|
}
|
|
20664
20862
|
}
|
|
@@ -20673,6 +20871,10 @@
|
|
|
20673
20871
|
the application unsanitized. Refuse to sanitize such a root
|
|
20674
20872
|
the same way we refuse a forbidden tag. GHSA-r47g-fvhr-h676. */
|
|
20675
20873
|
if (_isClobbered(dirty)) {
|
|
20874
|
+
/* Fail closed on a live clobbered root before throwing.
|
|
20875
|
+
_neutralizeRoot's reads are clobber-safe (cached getters); the
|
|
20876
|
+
form's non-clobbered descendants, e.g. an armed <img>, are scrubbed. */
|
|
20877
|
+
_neutralizeRoot(dirty);
|
|
20676
20878
|
throw typeErrorCreate('root node is clobbered and cannot be sanitized in-place');
|
|
20677
20879
|
}
|
|
20678
20880
|
/* Sanitize attached shadow roots before the main iterator runs.
|
|
@@ -20725,7 +20927,8 @@
|
|
|
20725
20927
|
_forceRemove(body.firstChild);
|
|
20726
20928
|
}
|
|
20727
20929
|
/* Get node iterator */
|
|
20728
|
-
const
|
|
20930
|
+
const walkRoot = inPlace ? dirty : body;
|
|
20931
|
+
const nodeIterator = _createNodeIterator(walkRoot);
|
|
20729
20932
|
/* Now start iterating over the created document.
|
|
20730
20933
|
The walk runs inside an exception barrier (campaign-3 F2): a re-entrant
|
|
20731
20934
|
engine/custom-element mutation can detach a node mid-walk so
|
|
@@ -20738,7 +20941,7 @@
|
|
|
20738
20941
|
try {
|
|
20739
20942
|
while (currentNode = nodeIterator.nextNode()) {
|
|
20740
20943
|
/* Sanitize tags and elements */
|
|
20741
|
-
_sanitizeElements(currentNode);
|
|
20944
|
+
_sanitizeElements(currentNode, walkRoot);
|
|
20742
20945
|
/* Check attributes next */
|
|
20743
20946
|
_sanitizeAttributes(currentNode);
|
|
20744
20947
|
/* Shadow DOM detected, sanitize it.
|
|
@@ -20752,6 +20955,14 @@
|
|
|
20752
20955
|
} catch (error) {
|
|
20753
20956
|
if (inPlace) {
|
|
20754
20957
|
_neutralizeRoot(dirty);
|
|
20958
|
+
/* Nodes _forceRemove'd earlier in the aborted walk are already
|
|
20959
|
+
detached from the root, so _neutralizeRoot's subtree pass does not
|
|
20960
|
+
reach them. Defuse them too, mirroring the success-path loop below. */
|
|
20961
|
+
arrayForEach(DOMPurify.removed, entry => {
|
|
20962
|
+
if (entry.element) {
|
|
20963
|
+
_neutralizeSubtree(entry.element);
|
|
20964
|
+
}
|
|
20965
|
+
});
|
|
20755
20966
|
}
|
|
20756
20967
|
throw error;
|
|
20757
20968
|
}
|
|
@@ -21349,44 +21560,22 @@
|
|
|
21349
21560
|
const bogus = get$a(element, 'data-mce-bogus');
|
|
21350
21561
|
if (!isInternalElement && isString(bogus)) {
|
|
21351
21562
|
if (bogus === 'all') {
|
|
21352
|
-
// Empty before removing so a detached raw-text element (e.g. script/style) can't carry
|
|
21353
|
-
// markup-like content into DOMPurify's unsafe-node check, which would then throw trying
|
|
21354
|
-
// to _forceRemove the now-parentless node.
|
|
21355
|
-
empty(element);
|
|
21356
21563
|
remove$8(element);
|
|
21357
21564
|
}
|
|
21358
21565
|
else {
|
|
21359
21566
|
unwrap(element);
|
|
21360
21567
|
}
|
|
21361
|
-
// We detach the element above; DOMPurify throws if it then tries to _forceRemove the
|
|
21362
|
-
// now-parentless node, so mark the tag allowed to stop that second removal.
|
|
21363
|
-
if (isNonNullable(evt)) {
|
|
21364
|
-
evt.allowedTags[lcTagName] = true;
|
|
21365
|
-
}
|
|
21366
21568
|
return;
|
|
21367
21569
|
}
|
|
21368
21570
|
// Determine if the schema allows the element and either add it or remove it
|
|
21369
21571
|
const rule = schema.getElementRule(lcTagName);
|
|
21370
21572
|
if (validate && !rule) {
|
|
21371
|
-
//
|
|
21372
|
-
|
|
21373
|
-
|
|
21374
|
-
if (settings.sanitize && (has$2(specialElements, lcTagName) || scope !== 'html')) {
|
|
21375
|
-
empty(element);
|
|
21573
|
+
// If a special element is invalid, then remove the entire element instead of unwrapping
|
|
21574
|
+
if (has$2(specialElements, lcTagName)) {
|
|
21575
|
+
remove$8(element);
|
|
21376
21576
|
}
|
|
21377
21577
|
else {
|
|
21378
|
-
|
|
21379
|
-
if (has$2(specialElements, lcTagName)) {
|
|
21380
|
-
remove$8(element);
|
|
21381
|
-
}
|
|
21382
|
-
else {
|
|
21383
|
-
unwrap(element);
|
|
21384
|
-
}
|
|
21385
|
-
// The element is now detached; mark the tag allowed so DOMPurify won't _forceRemove the
|
|
21386
|
-
// parentless node and throw. Safe here: it's empty and HTML-namespaced, so no earlier check fires.
|
|
21387
|
-
if (isNonNullable(evt)) {
|
|
21388
|
-
evt.allowedTags[lcTagName] = true;
|
|
21389
|
-
}
|
|
21578
|
+
unwrap(element);
|
|
21390
21579
|
}
|
|
21391
21580
|
return;
|
|
21392
21581
|
}
|
|
@@ -21569,9 +21758,7 @@
|
|
|
21569
21758
|
evt.allowedTags[lcTagName] = keepElement;
|
|
21570
21759
|
if (!keepElement && settings.sanitize) {
|
|
21571
21760
|
if (isElement$7(node)) {
|
|
21572
|
-
|
|
21573
|
-
// Detaching it would make DOMPurify throw when it _forceRemoves the parentless node.
|
|
21574
|
-
empty(SugarElement.fromDom(node));
|
|
21761
|
+
node.remove();
|
|
21575
21762
|
}
|
|
21576
21763
|
}
|
|
21577
21764
|
});
|
|
@@ -23193,14 +23380,21 @@
|
|
|
23193
23380
|
}
|
|
23194
23381
|
});
|
|
23195
23382
|
};
|
|
23196
|
-
// TODO: TINY-9142: Remove this to make nested noneditable formatting work
|
|
23197
23383
|
const targetNode = isNode(node) ? node : selection.getNode();
|
|
23198
23384
|
if (dom.getContentEditable(targetNode) === 'false' && !isWrappableNoneditable(ed, targetNode)) {
|
|
23199
23385
|
// node variable is used by other functions above in the same scope so need to set it here
|
|
23200
23386
|
node = targetNode;
|
|
23201
23387
|
applyNodeStyle(formatList, node);
|
|
23202
|
-
if (
|
|
23203
|
-
|
|
23388
|
+
if (hasEditableDescendants(dom, node)) {
|
|
23389
|
+
each$e(getEditableDescendants(dom, node), (island) => {
|
|
23390
|
+
const rng = dom.createRng();
|
|
23391
|
+
rng.selectNodeContents(island);
|
|
23392
|
+
applyRngStyle(dom, rng, true);
|
|
23393
|
+
});
|
|
23394
|
+
return;
|
|
23395
|
+
}
|
|
23396
|
+
if (isBlockFormat(format) && !dom.isBlock(node)) {
|
|
23397
|
+
const parentBlock = dom.getParent(node, dom.isBlock);
|
|
23204
23398
|
if (dom.isEditable(parentBlock)) {
|
|
23205
23399
|
const wrapperElementName = format.block;
|
|
23206
23400
|
if (parentBlock.nodeName.toLowerCase() === wrapperElementName.toLowerCase()) {
|
|
@@ -23212,6 +23406,12 @@
|
|
|
23212
23406
|
}
|
|
23213
23407
|
}
|
|
23214
23408
|
}
|
|
23409
|
+
else {
|
|
23410
|
+
const selectorFormats = filter$5(formatList, isSelectorFormat);
|
|
23411
|
+
Optional.from(dom.getParent(node, (candidate) => exists(selectorFormats, (fmt) => dom.is(candidate, fmt.selector))))
|
|
23412
|
+
.filter(dom.isEditable)
|
|
23413
|
+
.each((parent) => applyNodeStyle(formatList, parent));
|
|
23414
|
+
}
|
|
23215
23415
|
fireFormatApply(ed, name, node, vars);
|
|
23216
23416
|
return;
|
|
23217
23417
|
}
|
|
@@ -23245,7 +23445,7 @@
|
|
|
23245
23445
|
applyCaretFormat(ed, name, vars);
|
|
23246
23446
|
}
|
|
23247
23447
|
getExpandedListItemFormat(ed.formatter, name).each((liFmt) => {
|
|
23248
|
-
const list = getFullySelectedListItems(ed
|
|
23448
|
+
const list = getFullySelectedListItems(ed);
|
|
23249
23449
|
each$e(list, (li) => applyStyles(dom, li, liFmt, vars));
|
|
23250
23450
|
});
|
|
23251
23451
|
}
|
|
@@ -41882,14 +42082,14 @@
|
|
|
41882
42082
|
* @property minorVersion
|
|
41883
42083
|
* @type String
|
|
41884
42084
|
*/
|
|
41885
|
-
minorVersion: '8.
|
|
42085
|
+
minorVersion: '8.2',
|
|
41886
42086
|
/**
|
|
41887
42087
|
* Release date of TinyMCE build.
|
|
41888
42088
|
*
|
|
41889
42089
|
* @property releaseDate
|
|
41890
42090
|
* @type String
|
|
41891
42091
|
*/
|
|
41892
|
-
releaseDate: '2026-07-
|
|
42092
|
+
releaseDate: '2026-07-27',
|
|
41893
42093
|
/**
|
|
41894
42094
|
* Collection of language pack data.
|
|
41895
42095
|
*
|