tinymce-rails 7.3.0 → 7.4.1

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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/app/assets/source/tinymce/tinymce.js +1056 -980
  4. data/lib/tinymce/rails/version.rb +2 -2
  5. data/vendor/assets/javascripts/tinymce/icons/default/icons.js +1 -1
  6. data/vendor/assets/javascripts/tinymce/models/dom/model.js +2 -2
  7. data/vendor/assets/javascripts/tinymce/plugins/accordion/plugin.js +2 -2
  8. data/vendor/assets/javascripts/tinymce/plugins/advlist/plugin.js +2 -2
  9. data/vendor/assets/javascripts/tinymce/plugins/anchor/plugin.js +1 -1
  10. data/vendor/assets/javascripts/tinymce/plugins/autolink/plugin.js +2 -2
  11. data/vendor/assets/javascripts/tinymce/plugins/autoresize/plugin.js +1 -1
  12. data/vendor/assets/javascripts/tinymce/plugins/autosave/plugin.js +2 -2
  13. data/vendor/assets/javascripts/tinymce/plugins/charmap/plugin.js +1 -1
  14. data/vendor/assets/javascripts/tinymce/plugins/code/plugin.js +1 -1
  15. data/vendor/assets/javascripts/tinymce/plugins/codesample/plugin.js +1 -1
  16. data/vendor/assets/javascripts/tinymce/plugins/directionality/plugin.js +1 -1
  17. data/vendor/assets/javascripts/tinymce/plugins/emoticons/plugin.js +1 -1
  18. data/vendor/assets/javascripts/tinymce/plugins/fullscreen/plugin.js +2 -2
  19. data/vendor/assets/javascripts/tinymce/plugins/help/plugin.js +2 -2
  20. data/vendor/assets/javascripts/tinymce/plugins/image/plugin.js +2 -2
  21. data/vendor/assets/javascripts/tinymce/plugins/importcss/plugin.js +1 -1
  22. data/vendor/assets/javascripts/tinymce/plugins/insertdatetime/plugin.js +2 -2
  23. data/vendor/assets/javascripts/tinymce/plugins/link/plugin.js +2 -2
  24. data/vendor/assets/javascripts/tinymce/plugins/lists/plugin.js +2 -2
  25. data/vendor/assets/javascripts/tinymce/plugins/media/plugin.js +1 -1
  26. data/vendor/assets/javascripts/tinymce/plugins/nonbreaking/plugin.js +1 -1
  27. data/vendor/assets/javascripts/tinymce/plugins/pagebreak/plugin.js +1 -1
  28. data/vendor/assets/javascripts/tinymce/plugins/preview/plugin.js +2 -2
  29. data/vendor/assets/javascripts/tinymce/plugins/quickbars/plugin.js +1 -1
  30. data/vendor/assets/javascripts/tinymce/plugins/save/plugin.js +1 -1
  31. data/vendor/assets/javascripts/tinymce/plugins/searchreplace/plugin.js +1 -1
  32. data/vendor/assets/javascripts/tinymce/plugins/table/plugin.js +1 -1
  33. data/vendor/assets/javascripts/tinymce/plugins/visualblocks/plugin.js +2 -2
  34. data/vendor/assets/javascripts/tinymce/plugins/visualchars/plugin.js +2 -2
  35. data/vendor/assets/javascripts/tinymce/plugins/wordcount/plugin.js +2 -2
  36. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.css +1 -1
  37. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.inline.css +1 -1
  38. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.inline.js +1 -1
  39. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.inline.min.css +1 -1
  40. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.js +1 -1
  41. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.min.css +1 -1
  42. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.css +1 -1
  43. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.js +1 -1
  44. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.min.css +1 -1
  45. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.css +1 -1
  46. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.inline.css +1 -1
  47. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.inline.js +1 -1
  48. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.inline.min.css +1 -1
  49. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.js +1 -1
  50. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.min.css +1 -1
  51. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.css +1 -1
  52. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.js +1 -1
  53. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.min.css +1 -1
  54. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.css +1 -1
  55. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.inline.css +1 -1
  56. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.inline.js +1 -1
  57. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.inline.min.css +1 -1
  58. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.js +1 -1
  59. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.min.css +1 -1
  60. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.css +1 -1
  61. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.js +1 -1
  62. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.min.css +1 -1
  63. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.css +1 -1
  64. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.inline.css +1 -1
  65. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.inline.js +1 -1
  66. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.inline.min.css +1 -1
  67. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.js +1 -1
  68. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.min.css +1 -1
  69. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.css +1 -1
  70. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.js +1 -1
  71. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.min.css +1 -1
  72. data/vendor/assets/javascripts/tinymce/themes/silver/theme.js +384 -2
  73. data/vendor/assets/javascripts/tinymce/tinymce.d.ts +23 -0
  74. data/vendor/assets/javascripts/tinymce/tinymce.js +383 -2
  75. metadata +6 -6
@@ -1,5 +1,5 @@
1
1
  /**
2
- * TinyMCE version 7.3.0 (2024-08-07)
2
+ * TinyMCE version 7.4.1 (TBD)
3
3
  */
4
4
 
5
5
  (function () {
@@ -7290,6 +7290,19 @@
7290
7290
  processor: 'boolean',
7291
7291
  default: false
7292
7292
  });
7293
+ registerOption('allow_mathml_annotation_encodings', {
7294
+ processor: value => {
7295
+ const valid = isArrayOf(value, isString);
7296
+ return valid ? {
7297
+ value,
7298
+ valid
7299
+ } : {
7300
+ valid: false,
7301
+ message: 'Must be an array of strings.'
7302
+ };
7303
+ },
7304
+ default: []
7305
+ });
7293
7306
  registerOption('convert_fonts_to_spans', {
7294
7307
  processor: 'boolean',
7295
7308
  default: true,
@@ -7948,7 +7961,7 @@
7948
7961
  const isContentEditableTrue$1 = isContentEditableTrue$3;
7949
7962
  const isContentEditableFalse$7 = isContentEditableFalse$b;
7950
7963
  const isMedia = isMedia$2;
7951
- const isBlockLike = matchStyleValues('display', 'block table table-cell table-caption list-item');
7964
+ const isBlockLike = matchStyleValues('display', 'block table table-cell table-row table-caption list-item');
7952
7965
  const isCaretContainer = isCaretContainer$2;
7953
7966
  const isCaretContainerBlock = isCaretContainerBlock$1;
7954
7967
  const isElement$2 = isElement$6;
@@ -9635,7 +9648,7 @@
9635
9648
  };
9636
9649
  const isResizable = elm => {
9637
9650
  const selector = getObjectResizing(editor);
9638
- if (!selector) {
9651
+ if (!selector || editor.mode.isReadOnly()) {
9639
9652
  return false;
9640
9653
  }
9641
9654
  if (elm.getAttribute('data-mce-resize') === 'false') {
@@ -13243,6 +13256,9 @@
13243
13256
  if (node && node.attr('id') === 'mce_marker') {
13244
13257
  const marker = node;
13245
13258
  for (node = node.prev; node; node = node.walk(true)) {
13259
+ if (node.name === 'table') {
13260
+ break;
13261
+ }
13246
13262
  if (node.type === 3 || !dom.isBlock(node.name)) {
13247
13263
  if (node.parent && editor.schema.isValidChild(node.parent.name, 'span')) {
13248
13264
  node.parent.insert(marker, node, node.name === 'br');
@@ -15258,14 +15274,24 @@
15258
15274
  }
15259
15275
  };
15260
15276
 
15261
- const {entries, setPrototypeOf, isFrozen, getPrototypeOf, getOwnPropertyDescriptor} = Object;
15262
- let {freeze, seal, create: create$7} = Object;
15263
- let {apply, construct} = typeof Reflect !== 'undefined' && Reflect;
15264
- if (!apply) {
15265
- apply = function apply(fun, thisValue, args) {
15266
- return fun.apply(thisValue, args);
15267
- };
15268
- }
15277
+ /*! @license DOMPurify 3.1.7 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.7/LICENSE */
15278
+
15279
+ const {
15280
+ entries,
15281
+ setPrototypeOf,
15282
+ isFrozen,
15283
+ getPrototypeOf,
15284
+ getOwnPropertyDescriptor
15285
+ } = Object;
15286
+ let {
15287
+ freeze,
15288
+ seal,
15289
+ create: create$7
15290
+ } = Object; // eslint-disable-line import/no-mutable-exports
15291
+ let {
15292
+ apply,
15293
+ construct
15294
+ } = typeof Reflect !== 'undefined' && Reflect;
15269
15295
  if (!freeze) {
15270
15296
  freeze = function freeze(x) {
15271
15297
  return x;
@@ -15276,6 +15302,11 @@
15276
15302
  return x;
15277
15303
  };
15278
15304
  }
15305
+ if (!apply) {
15306
+ apply = function apply(fun, thisValue, args) {
15307
+ return fun.apply(thisValue, args);
15308
+ };
15309
+ }
15279
15310
  if (!construct) {
15280
15311
  construct = function construct(Func, args) {
15281
15312
  return new Func(...args);
@@ -15290,8 +15321,16 @@
15290
15321
  const stringReplace = unapply(String.prototype.replace);
15291
15322
  const stringIndexOf = unapply(String.prototype.indexOf);
15292
15323
  const stringTrim = unapply(String.prototype.trim);
15324
+ const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
15293
15325
  const regExpTest = unapply(RegExp.prototype.test);
15294
15326
  const typeErrorCreate = unconstruct(TypeError);
15327
+
15328
+ /**
15329
+ * Creates a new function that calls the given function with a specified thisArg and arguments.
15330
+ *
15331
+ * @param {Function} func - The function to be wrapped and called.
15332
+ * @returns {Function} A new function that calls the given function with a specified thisArg and arguments.
15333
+ */
15295
15334
  function unapply(func) {
15296
15335
  return function (thisArg) {
15297
15336
  for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
@@ -15300,6 +15339,13 @@
15300
15339
  return apply(func, thisArg, args);
15301
15340
  };
15302
15341
  }
15342
+
15343
+ /**
15344
+ * Creates a new function that constructs an instance of the given constructor function with the provided arguments.
15345
+ *
15346
+ * @param {Function} func - The constructor function to be wrapped and called.
15347
+ * @returns {Function} A new function that constructs an instance of the given constructor function with the provided arguments.
15348
+ */
15303
15349
  function unconstruct(func) {
15304
15350
  return function () {
15305
15351
  for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
@@ -15308,10 +15354,21 @@
15308
15354
  return construct(func, args);
15309
15355
  };
15310
15356
  }
15311
- function addToSet(set, array, transformCaseFunc) {
15312
- var _transformCaseFunc;
15313
- transformCaseFunc = (_transformCaseFunc = transformCaseFunc) !== null && _transformCaseFunc !== void 0 ? _transformCaseFunc : stringToLowerCase;
15357
+
15358
+ /**
15359
+ * Add properties to a lookup table
15360
+ *
15361
+ * @param {Object} set - The set to which elements will be added.
15362
+ * @param {Array} array - The array containing elements to be added to the set.
15363
+ * @param {Function} transformCaseFunc - An optional function to transform the case of each element before adding to the set.
15364
+ * @returns {Object} The modified set with added elements.
15365
+ */
15366
+ function addToSet(set, array) {
15367
+ let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;
15314
15368
  if (setPrototypeOf) {
15369
+ // Make 'in' and truthy checks like Boolean(set.constructor)
15370
+ // independent of any properties defined on Object.prototype.
15371
+ // Prevent prototype setters from intercepting set as a this value.
15315
15372
  setPrototypeOf(set, null);
15316
15373
  }
15317
15374
  let l = array.length;
@@ -15320,6 +15377,7 @@
15320
15377
  if (typeof element === 'string') {
15321
15378
  const lcElement = transformCaseFunc(element);
15322
15379
  if (lcElement !== element) {
15380
+ // Config presets (e.g. tags.js, attrs.js) are immutable.
15323
15381
  if (!isFrozen(array)) {
15324
15382
  array[l] = lcElement;
15325
15383
  }
@@ -15330,13 +15388,53 @@
15330
15388
  }
15331
15389
  return set;
15332
15390
  }
15391
+
15392
+ /**
15393
+ * Clean up an array to harden against CSPP
15394
+ *
15395
+ * @param {Array} array - The array to be cleaned.
15396
+ * @returns {Array} The cleaned version of the array
15397
+ */
15398
+ function cleanArray(array) {
15399
+ for (let index = 0; index < array.length; index++) {
15400
+ const isPropertyExist = objectHasOwnProperty(array, index);
15401
+ if (!isPropertyExist) {
15402
+ array[index] = null;
15403
+ }
15404
+ }
15405
+ return array;
15406
+ }
15407
+
15408
+ /**
15409
+ * Shallow clone an object
15410
+ *
15411
+ * @param {Object} object - The object to be cloned.
15412
+ * @returns {Object} A new object that copies the original.
15413
+ */
15333
15414
  function clone(object) {
15334
15415
  const newObject = create$7(null);
15335
15416
  for (const [property, value] of entries(object)) {
15336
- newObject[property] = value;
15417
+ const isPropertyExist = objectHasOwnProperty(object, property);
15418
+ if (isPropertyExist) {
15419
+ if (Array.isArray(value)) {
15420
+ newObject[property] = cleanArray(value);
15421
+ } else if (value && typeof value === 'object' && value.constructor === Object) {
15422
+ newObject[property] = clone(value);
15423
+ } else {
15424
+ newObject[property] = value;
15425
+ }
15426
+ }
15337
15427
  }
15338
15428
  return newObject;
15339
15429
  }
15430
+
15431
+ /**
15432
+ * This method automatically checks if the prop is function or getter and behaves accordingly.
15433
+ *
15434
+ * @param {Object} object - The object to look up the getter function in its prototype chain.
15435
+ * @param {String} prop - The property name for which to find the getter function.
15436
+ * @returns {Function} The getter function found in the prototype chain or a fallback function.
15437
+ */
15340
15438
  function lookupGetter(object, prop) {
15341
15439
  while (object !== null) {
15342
15440
  const desc = getOwnPropertyDescriptor(object, prop);
@@ -15350,644 +15448,50 @@
15350
15448
  }
15351
15449
  object = getPrototypeOf(object);
15352
15450
  }
15353
- function fallbackValue(element) {
15354
- console.warn('fallback value for', element);
15451
+ function fallbackValue() {
15355
15452
  return null;
15356
15453
  }
15357
15454
  return fallbackValue;
15358
15455
  }
15359
- const html$1 = freeze([
15360
- 'a',
15361
- 'abbr',
15362
- 'acronym',
15363
- 'address',
15364
- 'area',
15365
- 'article',
15366
- 'aside',
15367
- 'audio',
15368
- 'b',
15369
- 'bdi',
15370
- 'bdo',
15371
- 'big',
15372
- 'blink',
15373
- 'blockquote',
15374
- 'body',
15375
- 'br',
15376
- 'button',
15377
- 'canvas',
15378
- 'caption',
15379
- 'center',
15380
- 'cite',
15381
- 'code',
15382
- 'col',
15383
- 'colgroup',
15384
- 'content',
15385
- 'data',
15386
- 'datalist',
15387
- 'dd',
15388
- 'decorator',
15389
- 'del',
15390
- 'details',
15391
- 'dfn',
15392
- 'dialog',
15393
- 'dir',
15394
- 'div',
15395
- 'dl',
15396
- 'dt',
15397
- 'element',
15398
- 'em',
15399
- 'fieldset',
15400
- 'figcaption',
15401
- 'figure',
15402
- 'font',
15403
- 'footer',
15404
- 'form',
15405
- 'h1',
15406
- 'h2',
15407
- 'h3',
15408
- 'h4',
15409
- 'h5',
15410
- 'h6',
15411
- 'head',
15412
- 'header',
15413
- 'hgroup',
15414
- 'hr',
15415
- 'html',
15416
- 'i',
15417
- 'img',
15418
- 'input',
15419
- 'ins',
15420
- 'kbd',
15421
- 'label',
15422
- 'legend',
15423
- 'li',
15424
- 'main',
15425
- 'map',
15426
- 'mark',
15427
- 'marquee',
15428
- 'menu',
15429
- 'menuitem',
15430
- 'meter',
15431
- 'nav',
15432
- 'nobr',
15433
- 'ol',
15434
- 'optgroup',
15435
- 'option',
15436
- 'output',
15437
- 'p',
15438
- 'picture',
15439
- 'pre',
15440
- 'progress',
15441
- 'q',
15442
- 'rp',
15443
- 'rt',
15444
- 'ruby',
15445
- 's',
15446
- 'samp',
15447
- 'section',
15448
- 'select',
15449
- 'shadow',
15450
- 'small',
15451
- 'source',
15452
- 'spacer',
15453
- 'span',
15454
- 'strike',
15455
- 'strong',
15456
- 'style',
15457
- 'sub',
15458
- 'summary',
15459
- 'sup',
15460
- 'table',
15461
- 'tbody',
15462
- 'td',
15463
- 'template',
15464
- 'textarea',
15465
- 'tfoot',
15466
- 'th',
15467
- 'thead',
15468
- 'time',
15469
- 'tr',
15470
- 'track',
15471
- 'tt',
15472
- 'u',
15473
- 'ul',
15474
- 'var',
15475
- 'video',
15476
- 'wbr'
15477
- ]);
15478
- const svg$1 = freeze([
15479
- 'svg',
15480
- 'a',
15481
- 'altglyph',
15482
- 'altglyphdef',
15483
- 'altglyphitem',
15484
- 'animatecolor',
15485
- 'animatemotion',
15486
- 'animatetransform',
15487
- 'circle',
15488
- 'clippath',
15489
- 'defs',
15490
- 'desc',
15491
- 'ellipse',
15492
- 'filter',
15493
- 'font',
15494
- 'g',
15495
- 'glyph',
15496
- 'glyphref',
15497
- 'hkern',
15498
- 'image',
15499
- 'line',
15500
- 'lineargradient',
15501
- 'marker',
15502
- 'mask',
15503
- 'metadata',
15504
- 'mpath',
15505
- 'path',
15506
- 'pattern',
15507
- 'polygon',
15508
- 'polyline',
15509
- 'radialgradient',
15510
- 'rect',
15511
- 'stop',
15512
- 'style',
15513
- 'switch',
15514
- 'symbol',
15515
- 'text',
15516
- 'textpath',
15517
- 'title',
15518
- 'tref',
15519
- 'tspan',
15520
- 'view',
15521
- 'vkern'
15522
- ]);
15523
- const svgFilters = freeze([
15524
- 'feBlend',
15525
- 'feColorMatrix',
15526
- 'feComponentTransfer',
15527
- 'feComposite',
15528
- 'feConvolveMatrix',
15529
- 'feDiffuseLighting',
15530
- 'feDisplacementMap',
15531
- 'feDistantLight',
15532
- 'feDropShadow',
15533
- 'feFlood',
15534
- 'feFuncA',
15535
- 'feFuncB',
15536
- 'feFuncG',
15537
- 'feFuncR',
15538
- 'feGaussianBlur',
15539
- 'feImage',
15540
- 'feMerge',
15541
- 'feMergeNode',
15542
- 'feMorphology',
15543
- 'feOffset',
15544
- 'fePointLight',
15545
- 'feSpecularLighting',
15546
- 'feSpotLight',
15547
- 'feTile',
15548
- 'feTurbulence'
15549
- ]);
15550
- const svgDisallowed = freeze([
15551
- 'animate',
15552
- 'color-profile',
15553
- 'cursor',
15554
- 'discard',
15555
- 'font-face',
15556
- 'font-face-format',
15557
- 'font-face-name',
15558
- 'font-face-src',
15559
- 'font-face-uri',
15560
- 'foreignobject',
15561
- 'hatch',
15562
- 'hatchpath',
15563
- 'mesh',
15564
- 'meshgradient',
15565
- 'meshpatch',
15566
- 'meshrow',
15567
- 'missing-glyph',
15568
- 'script',
15569
- 'set',
15570
- 'solidcolor',
15571
- 'unknown',
15572
- 'use'
15573
- ]);
15574
- const mathMl$1 = freeze([
15575
- 'math',
15576
- 'menclose',
15577
- 'merror',
15578
- 'mfenced',
15579
- 'mfrac',
15580
- 'mglyph',
15581
- 'mi',
15582
- 'mlabeledtr',
15583
- 'mmultiscripts',
15584
- 'mn',
15585
- 'mo',
15586
- 'mover',
15587
- 'mpadded',
15588
- 'mphantom',
15589
- 'mroot',
15590
- 'mrow',
15591
- 'ms',
15592
- 'mspace',
15593
- 'msqrt',
15594
- 'mstyle',
15595
- 'msub',
15596
- 'msup',
15597
- 'msubsup',
15598
- 'mtable',
15599
- 'mtd',
15600
- 'mtext',
15601
- 'mtr',
15602
- 'munder',
15603
- 'munderover',
15604
- 'mprescripts'
15605
- ]);
15606
- const mathMlDisallowed = freeze([
15607
- 'maction',
15608
- 'maligngroup',
15609
- 'malignmark',
15610
- 'mlongdiv',
15611
- 'mscarries',
15612
- 'mscarry',
15613
- 'msgroup',
15614
- 'mstack',
15615
- 'msline',
15616
- 'msrow',
15617
- 'semantics',
15618
- 'annotation',
15619
- 'annotation-xml',
15620
- 'mprescripts',
15621
- 'none'
15622
- ]);
15456
+
15457
+ const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
15458
+
15459
+ // SVG
15460
+ const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
15461
+ const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);
15462
+
15463
+ // List of SVG elements that are disallowed by default.
15464
+ // We still need to know them so that we can do namespace
15465
+ // checks properly in case one wants to add them to
15466
+ // allow-list.
15467
+ const svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);
15468
+ const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);
15469
+
15470
+ // Similarly to SVG, we want to know all MathML elements,
15471
+ // even those that we disallow by default.
15472
+ const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
15623
15473
  const text = freeze(['#text']);
15624
- const html = freeze([
15625
- 'accept',
15626
- 'action',
15627
- 'align',
15628
- 'alt',
15629
- 'autocapitalize',
15630
- 'autocomplete',
15631
- 'autopictureinpicture',
15632
- 'autoplay',
15633
- 'background',
15634
- 'bgcolor',
15635
- 'border',
15636
- 'capture',
15637
- 'cellpadding',
15638
- 'cellspacing',
15639
- 'checked',
15640
- 'cite',
15641
- 'class',
15642
- 'clear',
15643
- 'color',
15644
- 'cols',
15645
- 'colspan',
15646
- 'controls',
15647
- 'controlslist',
15648
- 'coords',
15649
- 'crossorigin',
15650
- 'datetime',
15651
- 'decoding',
15652
- 'default',
15653
- 'dir',
15654
- 'disabled',
15655
- 'disablepictureinpicture',
15656
- 'disableremoteplayback',
15657
- 'download',
15658
- 'draggable',
15659
- 'enctype',
15660
- 'enterkeyhint',
15661
- 'face',
15662
- 'for',
15663
- 'headers',
15664
- 'height',
15665
- 'hidden',
15666
- 'high',
15667
- 'href',
15668
- 'hreflang',
15669
- 'id',
15670
- 'inputmode',
15671
- 'integrity',
15672
- 'ismap',
15673
- 'kind',
15674
- 'label',
15675
- 'lang',
15676
- 'list',
15677
- 'loading',
15678
- 'loop',
15679
- 'low',
15680
- 'max',
15681
- 'maxlength',
15682
- 'media',
15683
- 'method',
15684
- 'min',
15685
- 'minlength',
15686
- 'multiple',
15687
- 'muted',
15688
- 'name',
15689
- 'nonce',
15690
- 'noshade',
15691
- 'novalidate',
15692
- 'nowrap',
15693
- 'open',
15694
- 'optimum',
15695
- 'pattern',
15696
- 'placeholder',
15697
- 'playsinline',
15698
- 'poster',
15699
- 'preload',
15700
- 'pubdate',
15701
- 'radiogroup',
15702
- 'readonly',
15703
- 'rel',
15704
- 'required',
15705
- 'rev',
15706
- 'reversed',
15707
- 'role',
15708
- 'rows',
15709
- 'rowspan',
15710
- 'spellcheck',
15711
- 'scope',
15712
- 'selected',
15713
- 'shape',
15714
- 'size',
15715
- 'sizes',
15716
- 'span',
15717
- 'srclang',
15718
- 'start',
15719
- 'src',
15720
- 'srcset',
15721
- 'step',
15722
- 'style',
15723
- 'summary',
15724
- 'tabindex',
15725
- 'title',
15726
- 'translate',
15727
- 'type',
15728
- 'usemap',
15729
- 'valign',
15730
- 'value',
15731
- 'width',
15732
- 'xmlns',
15733
- 'slot'
15734
- ]);
15735
- const svg = freeze([
15736
- 'accent-height',
15737
- 'accumulate',
15738
- 'additive',
15739
- 'alignment-baseline',
15740
- 'ascent',
15741
- 'attributename',
15742
- 'attributetype',
15743
- 'azimuth',
15744
- 'basefrequency',
15745
- 'baseline-shift',
15746
- 'begin',
15747
- 'bias',
15748
- 'by',
15749
- 'class',
15750
- 'clip',
15751
- 'clippathunits',
15752
- 'clip-path',
15753
- 'clip-rule',
15754
- 'color',
15755
- 'color-interpolation',
15756
- 'color-interpolation-filters',
15757
- 'color-profile',
15758
- 'color-rendering',
15759
- 'cx',
15760
- 'cy',
15761
- 'd',
15762
- 'dx',
15763
- 'dy',
15764
- 'diffuseconstant',
15765
- 'direction',
15766
- 'display',
15767
- 'divisor',
15768
- 'dur',
15769
- 'edgemode',
15770
- 'elevation',
15771
- 'end',
15772
- 'fill',
15773
- 'fill-opacity',
15774
- 'fill-rule',
15775
- 'filter',
15776
- 'filterunits',
15777
- 'flood-color',
15778
- 'flood-opacity',
15779
- 'font-family',
15780
- 'font-size',
15781
- 'font-size-adjust',
15782
- 'font-stretch',
15783
- 'font-style',
15784
- 'font-variant',
15785
- 'font-weight',
15786
- 'fx',
15787
- 'fy',
15788
- 'g1',
15789
- 'g2',
15790
- 'glyph-name',
15791
- 'glyphref',
15792
- 'gradientunits',
15793
- 'gradienttransform',
15794
- 'height',
15795
- 'href',
15796
- 'id',
15797
- 'image-rendering',
15798
- 'in',
15799
- 'in2',
15800
- 'k',
15801
- 'k1',
15802
- 'k2',
15803
- 'k3',
15804
- 'k4',
15805
- 'kerning',
15806
- 'keypoints',
15807
- 'keysplines',
15808
- 'keytimes',
15809
- 'lang',
15810
- 'lengthadjust',
15811
- 'letter-spacing',
15812
- 'kernelmatrix',
15813
- 'kernelunitlength',
15814
- 'lighting-color',
15815
- 'local',
15816
- 'marker-end',
15817
- 'marker-mid',
15818
- 'marker-start',
15819
- 'markerheight',
15820
- 'markerunits',
15821
- 'markerwidth',
15822
- 'maskcontentunits',
15823
- 'maskunits',
15824
- 'max',
15825
- 'mask',
15826
- 'media',
15827
- 'method',
15828
- 'mode',
15829
- 'min',
15830
- 'name',
15831
- 'numoctaves',
15832
- 'offset',
15833
- 'operator',
15834
- 'opacity',
15835
- 'order',
15836
- 'orient',
15837
- 'orientation',
15838
- 'origin',
15839
- 'overflow',
15840
- 'paint-order',
15841
- 'path',
15842
- 'pathlength',
15843
- 'patterncontentunits',
15844
- 'patterntransform',
15845
- 'patternunits',
15846
- 'points',
15847
- 'preservealpha',
15848
- 'preserveaspectratio',
15849
- 'primitiveunits',
15850
- 'r',
15851
- 'rx',
15852
- 'ry',
15853
- 'radius',
15854
- 'refx',
15855
- 'refy',
15856
- 'repeatcount',
15857
- 'repeatdur',
15858
- 'restart',
15859
- 'result',
15860
- 'rotate',
15861
- 'scale',
15862
- 'seed',
15863
- 'shape-rendering',
15864
- 'specularconstant',
15865
- 'specularexponent',
15866
- 'spreadmethod',
15867
- 'startoffset',
15868
- 'stddeviation',
15869
- 'stitchtiles',
15870
- 'stop-color',
15871
- 'stop-opacity',
15872
- 'stroke-dasharray',
15873
- 'stroke-dashoffset',
15874
- 'stroke-linecap',
15875
- 'stroke-linejoin',
15876
- 'stroke-miterlimit',
15877
- 'stroke-opacity',
15878
- 'stroke',
15879
- 'stroke-width',
15880
- 'style',
15881
- 'surfacescale',
15882
- 'systemlanguage',
15883
- 'tabindex',
15884
- 'targetx',
15885
- 'targety',
15886
- 'transform',
15887
- 'transform-origin',
15888
- 'text-anchor',
15889
- 'text-decoration',
15890
- 'text-rendering',
15891
- 'textlength',
15892
- 'type',
15893
- 'u1',
15894
- 'u2',
15895
- 'unicode',
15896
- 'values',
15897
- 'viewbox',
15898
- 'visibility',
15899
- 'version',
15900
- 'vert-adv-y',
15901
- 'vert-origin-x',
15902
- 'vert-origin-y',
15903
- 'width',
15904
- 'word-spacing',
15905
- 'wrap',
15906
- 'writing-mode',
15907
- 'xchannelselector',
15908
- 'ychannelselector',
15909
- 'x',
15910
- 'x1',
15911
- 'x2',
15912
- 'xmlns',
15913
- 'y',
15914
- 'y1',
15915
- 'y2',
15916
- 'z',
15917
- 'zoomandpan'
15918
- ]);
15919
- const mathMl = freeze([
15920
- 'accent',
15921
- 'accentunder',
15922
- 'align',
15923
- 'bevelled',
15924
- 'close',
15925
- 'columnsalign',
15926
- 'columnlines',
15927
- 'columnspan',
15928
- 'denomalign',
15929
- 'depth',
15930
- 'dir',
15931
- 'display',
15932
- 'displaystyle',
15933
- 'encoding',
15934
- 'fence',
15935
- 'frame',
15936
- 'height',
15937
- 'href',
15938
- 'id',
15939
- 'largeop',
15940
- 'length',
15941
- 'linethickness',
15942
- 'lspace',
15943
- 'lquote',
15944
- 'mathbackground',
15945
- 'mathcolor',
15946
- 'mathsize',
15947
- 'mathvariant',
15948
- 'maxsize',
15949
- 'minsize',
15950
- 'movablelimits',
15951
- 'notation',
15952
- 'numalign',
15953
- 'open',
15954
- 'rowalign',
15955
- 'rowlines',
15956
- 'rowspacing',
15957
- 'rowspan',
15958
- 'rspace',
15959
- 'rquote',
15960
- 'scriptlevel',
15961
- 'scriptminsize',
15962
- 'scriptsizemultiplier',
15963
- 'selection',
15964
- 'separator',
15965
- 'separators',
15966
- 'stretchy',
15967
- 'subscriptshift',
15968
- 'supscriptshift',
15969
- 'symmetric',
15970
- 'voffset',
15971
- 'width',
15972
- 'xmlns'
15973
- ]);
15974
- const xml = freeze([
15975
- 'xlink:href',
15976
- 'xml:id',
15977
- 'xlink:title',
15978
- 'xml:space',
15979
- 'xmlns:xlink'
15980
- ]);
15981
- const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm);
15474
+
15475
+ const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', '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', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);
15476
+ 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', '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']);
15477
+ const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', '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']);
15478
+ const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
15479
+
15480
+ // eslint-disable-next-line unicorn/better-regex
15481
+ const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
15982
15482
  const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
15983
15483
  const TMPLIT_EXPR = seal(/\${[\w\W]*}/gm);
15984
- const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/);
15985
- const ARIA_ATTR = seal(/^aria-[\-\w]+$/);
15986
- const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i);
15484
+ const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape
15485
+ const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
15486
+ const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
15487
+ );
15987
15488
  const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
15988
- const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g);
15489
+ const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
15490
+ );
15989
15491
  const DOCTYPE_NAME = seal(/^html$/i);
15990
- var EXPRESSIONS = Object.freeze({
15492
+ const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
15493
+
15494
+ var EXPRESSIONS = /*#__PURE__*/Object.freeze({
15991
15495
  __proto__: null,
15992
15496
  MUSTACHE_EXPR: MUSTACHE_EXPR,
15993
15497
  ERB_EXPR: ERB_EXPR,
@@ -15997,13 +15501,47 @@
15997
15501
  IS_ALLOWED_URI: IS_ALLOWED_URI,
15998
15502
  IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
15999
15503
  ATTR_WHITESPACE: ATTR_WHITESPACE,
16000
- DOCTYPE_NAME: DOCTYPE_NAME
15504
+ DOCTYPE_NAME: DOCTYPE_NAME,
15505
+ CUSTOM_ELEMENT: CUSTOM_ELEMENT
16001
15506
  });
16002
- const getGlobal = () => typeof window === 'undefined' ? null : window;
15507
+
15508
+ // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
15509
+ const NODE_TYPE = {
15510
+ element: 1,
15511
+ attribute: 2,
15512
+ text: 3,
15513
+ cdataSection: 4,
15514
+ entityReference: 5,
15515
+ // Deprecated
15516
+ entityNode: 6,
15517
+ // Deprecated
15518
+ progressingInstruction: 7,
15519
+ comment: 8,
15520
+ document: 9,
15521
+ documentType: 10,
15522
+ documentFragment: 11,
15523
+ notation: 12 // Deprecated
15524
+ };
15525
+ const getGlobal = function getGlobal() {
15526
+ return typeof window === 'undefined' ? null : window;
15527
+ };
15528
+
15529
+ /**
15530
+ * Creates a no-op policy for internal use only.
15531
+ * Don't export this function outside this module!
15532
+ * @param {TrustedTypePolicyFactory} trustedTypes The policy factory.
15533
+ * @param {HTMLScriptElement} purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
15534
+ * @return {TrustedTypePolicy} The policy created (or null, if Trusted Types
15535
+ * are not supported or creating the policy failed).
15536
+ */
16003
15537
  const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {
16004
15538
  if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
16005
15539
  return null;
16006
15540
  }
15541
+
15542
+ // Allow the callers to control the unique policy name
15543
+ // by adding a data-tt-policy-suffix to the script element with the DOMPurify.
15544
+ // Policy creation with duplicate names throws in Trusted Types.
16007
15545
  let suffix = null;
16008
15546
  const ATTR_NAME = 'data-tt-policy-suffix';
16009
15547
  if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {
@@ -16020,6 +15558,9 @@
16020
15558
  }
16021
15559
  });
16022
15560
  } catch (_) {
15561
+ // Policy creation failed (most likely another DOMPurify script has
15562
+ // already run). Skip creating the policy, as this will only cause errors
15563
+ // if TT are enforced.
16023
15564
  console.warn('TrustedTypes policy ' + policyName + ' could not be created.');
16024
15565
  return null;
16025
15566
  }
@@ -16027,21 +15568,53 @@
16027
15568
  function createDOMPurify() {
16028
15569
  let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
16029
15570
  const DOMPurify = root => createDOMPurify(root);
16030
- DOMPurify.version = '3.0.5';
15571
+
15572
+ /**
15573
+ * Version label, exposed for easier checks
15574
+ * if DOMPurify is up to date or not
15575
+ */
15576
+ DOMPurify.version = '3.1.7';
15577
+
15578
+ /**
15579
+ * Array of elements that DOMPurify removed during sanitation.
15580
+ * Empty if nothing was removed.
15581
+ */
16031
15582
  DOMPurify.removed = [];
16032
- if (!window || !window.document || window.document.nodeType !== 9) {
15583
+ if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document) {
15584
+ // Not running in a browser, provide a factory function
15585
+ // so that you can pass your own Window
16033
15586
  DOMPurify.isSupported = false;
16034
15587
  return DOMPurify;
16035
15588
  }
16036
- const originalDocument = window.document;
15589
+ let {
15590
+ document
15591
+ } = window;
15592
+ const originalDocument = document;
16037
15593
  const currentScript = originalDocument.currentScript;
16038
- let {document} = window;
16039
- const {DocumentFragment, HTMLTemplateElement, Node, Element, NodeFilter, NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap, HTMLFormElement, DOMParser, trustedTypes} = window;
15594
+ const {
15595
+ DocumentFragment,
15596
+ HTMLTemplateElement,
15597
+ Node,
15598
+ Element,
15599
+ NodeFilter,
15600
+ NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
15601
+ HTMLFormElement,
15602
+ DOMParser,
15603
+ trustedTypes
15604
+ } = window;
16040
15605
  const ElementPrototype = Element.prototype;
16041
15606
  const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
15607
+ const remove = lookupGetter(ElementPrototype, 'remove');
16042
15608
  const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
16043
15609
  const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
16044
15610
  const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
15611
+
15612
+ // As per issue #47, the web-components registry is inherited by a
15613
+ // new document created via createHTMLDocument. As per the spec
15614
+ // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
15615
+ // a new empty registry is used when creating a template contents owner
15616
+ // document, so we use that as our parent document to ensure nothing
15617
+ // is inherited.
16045
15618
  if (typeof HTMLTemplateElement === 'function') {
16046
15619
  const template = document.createElement('template');
16047
15620
  if (template.content && template.content.ownerDocument) {
@@ -16050,28 +15623,55 @@
16050
15623
  }
16051
15624
  let trustedTypesPolicy;
16052
15625
  let emptyHTML = '';
16053
- const {implementation, createNodeIterator, createDocumentFragment, getElementsByTagName} = document;
16054
- const {importNode} = originalDocument;
15626
+ const {
15627
+ implementation,
15628
+ createNodeIterator,
15629
+ createDocumentFragment,
15630
+ getElementsByTagName
15631
+ } = document;
15632
+ const {
15633
+ importNode
15634
+ } = originalDocument;
16055
15635
  let hooks = {};
15636
+
15637
+ /**
15638
+ * Expose whether this browser supports running the full DOMPurify.
15639
+ */
16056
15640
  DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
16057
- const {MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR, DATA_ATTR, ARIA_ATTR, IS_SCRIPT_OR_DATA, ATTR_WHITESPACE} = EXPRESSIONS;
16058
- let {IS_ALLOWED_URI: IS_ALLOWED_URI$1} = EXPRESSIONS;
15641
+ const {
15642
+ MUSTACHE_EXPR,
15643
+ ERB_EXPR,
15644
+ TMPLIT_EXPR,
15645
+ DATA_ATTR,
15646
+ ARIA_ATTR,
15647
+ IS_SCRIPT_OR_DATA,
15648
+ ATTR_WHITESPACE,
15649
+ CUSTOM_ELEMENT
15650
+ } = EXPRESSIONS;
15651
+ let {
15652
+ IS_ALLOWED_URI: IS_ALLOWED_URI$1
15653
+ } = EXPRESSIONS;
15654
+
15655
+ /**
15656
+ * We consider the elements and attributes below to be safe. Ideally
15657
+ * don't add any new ones but feel free to remove unwanted ones.
15658
+ */
15659
+
15660
+ /* allowed element names */
16059
15661
  let ALLOWED_TAGS = null;
16060
- const DEFAULT_ALLOWED_TAGS = addToSet({}, [
16061
- ...html$1,
16062
- ...svg$1,
16063
- ...svgFilters,
16064
- ...mathMl$1,
16065
- ...text
16066
- ]);
15662
+ const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);
15663
+
15664
+ /* Allowed attribute names */
16067
15665
  let ALLOWED_ATTR = null;
16068
- const DEFAULT_ALLOWED_ATTR = addToSet({}, [
16069
- ...html,
16070
- ...svg,
16071
- ...mathMl,
16072
- ...xml
16073
- ]);
16074
- let CUSTOM_ELEMENT_HANDLING = Object.seal(Object.create(null, {
15666
+ const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);
15667
+
15668
+ /*
15669
+ * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements.
15670
+ * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
15671
+ * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
15672
+ * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
15673
+ */
15674
+ let CUSTOM_ELEMENT_HANDLING = Object.seal(create$7(null, {
16075
15675
  tagNameCheck: {
16076
15676
  writable: true,
16077
15677
  configurable: false,
@@ -16091,135 +15691,193 @@
16091
15691
  value: false
16092
15692
  }
16093
15693
  }));
15694
+
15695
+ /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
16094
15696
  let FORBID_TAGS = null;
15697
+
15698
+ /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
16095
15699
  let FORBID_ATTR = null;
15700
+
15701
+ /* Decide if ARIA attributes are okay */
16096
15702
  let ALLOW_ARIA_ATTR = true;
15703
+
15704
+ /* Decide if custom data attributes are okay */
16097
15705
  let ALLOW_DATA_ATTR = true;
15706
+
15707
+ /* Decide if unknown protocols are okay */
16098
15708
  let ALLOW_UNKNOWN_PROTOCOLS = false;
15709
+
15710
+ /* Decide if self-closing tags in attributes are allowed.
15711
+ * Usually removed due to a mXSS issue in jQuery 3.0 */
16099
15712
  let ALLOW_SELF_CLOSE_IN_ATTR = true;
15713
+
15714
+ /* Output should be safe for common template engines.
15715
+ * This means, DOMPurify removes data attributes, mustaches and ERB
15716
+ */
16100
15717
  let SAFE_FOR_TEMPLATES = false;
15718
+
15719
+ /* Output should be safe even for XML used within HTML and alike.
15720
+ * This means, DOMPurify removes comments when containing risky content.
15721
+ */
15722
+ let SAFE_FOR_XML = true;
15723
+
15724
+ /* Decide if document with <html>... should be returned */
16101
15725
  let WHOLE_DOCUMENT = false;
15726
+
15727
+ /* Track whether config is already set on this instance of DOMPurify. */
16102
15728
  let SET_CONFIG = false;
15729
+
15730
+ /* Decide if all elements (e.g. style, script) must be children of
15731
+ * document.body. By default, browsers might move them to document.head */
16103
15732
  let FORCE_BODY = false;
15733
+
15734
+ /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
15735
+ * string (or a TrustedHTML object if Trusted Types are supported).
15736
+ * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
15737
+ */
16104
15738
  let RETURN_DOM = false;
15739
+
15740
+ /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
15741
+ * string (or a TrustedHTML object if Trusted Types are supported) */
16105
15742
  let RETURN_DOM_FRAGMENT = false;
15743
+
15744
+ /* Try to return a Trusted Type object instead of a string, return a string in
15745
+ * case Trusted Types are not supported */
16106
15746
  let RETURN_TRUSTED_TYPE = false;
15747
+
15748
+ /* Output should be free from DOM clobbering attacks?
15749
+ * This sanitizes markups named with colliding, clobberable built-in DOM APIs.
15750
+ */
16107
15751
  let SANITIZE_DOM = true;
15752
+
15753
+ /* Achieve full DOM Clobbering protection by isolating the namespace of named
15754
+ * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
15755
+ *
15756
+ * HTML/DOM spec rules that enable DOM Clobbering:
15757
+ * - Named Access on Window (§7.3.3)
15758
+ * - DOM Tree Accessors (§3.1.5)
15759
+ * - Form Element Parent-Child Relations (§4.10.3)
15760
+ * - Iframe srcdoc / Nested WindowProxies (§4.8.5)
15761
+ * - HTMLCollection (§4.2.10.2)
15762
+ *
15763
+ * Namespace isolation is implemented by prefixing `id` and `name` attributes
15764
+ * with a constant string, i.e., `user-content-`
15765
+ */
16108
15766
  let SANITIZE_NAMED_PROPS = false;
16109
15767
  const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
15768
+
15769
+ /* Keep element content when removing element? */
16110
15770
  let KEEP_CONTENT = true;
15771
+
15772
+ /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
15773
+ * of importing it into a new Document and returning a sanitized copy */
16111
15774
  let IN_PLACE = false;
15775
+
15776
+ /* Allow usage of profiles like html, svg and mathMl */
16112
15777
  let USE_PROFILES = {};
15778
+
15779
+ /* Tags to ignore content of when KEEP_CONTENT is true */
16113
15780
  let FORBID_CONTENTS = null;
16114
- const DEFAULT_FORBID_CONTENTS = addToSet({}, [
16115
- 'annotation-xml',
16116
- 'audio',
16117
- 'colgroup',
16118
- 'desc',
16119
- 'foreignobject',
16120
- 'head',
16121
- 'iframe',
16122
- 'math',
16123
- 'mi',
16124
- 'mn',
16125
- 'mo',
16126
- 'ms',
16127
- 'mtext',
16128
- 'noembed',
16129
- 'noframes',
16130
- 'noscript',
16131
- 'plaintext',
16132
- 'script',
16133
- 'style',
16134
- 'svg',
16135
- 'template',
16136
- 'thead',
16137
- 'title',
16138
- 'video',
16139
- 'xmp'
16140
- ]);
15781
+ const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
15782
+
15783
+ /* Tags that are safe for data: URIs */
16141
15784
  let DATA_URI_TAGS = null;
16142
- const DEFAULT_DATA_URI_TAGS = addToSet({}, [
16143
- 'audio',
16144
- 'video',
16145
- 'img',
16146
- 'source',
16147
- 'image',
16148
- 'track'
16149
- ]);
15785
+ const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
15786
+
15787
+ /* Attributes safe for values like "javascript:" */
16150
15788
  let URI_SAFE_ATTRIBUTES = null;
16151
- const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, [
16152
- 'alt',
16153
- 'class',
16154
- 'for',
16155
- 'id',
16156
- 'label',
16157
- 'name',
16158
- 'pattern',
16159
- 'placeholder',
16160
- 'role',
16161
- 'summary',
16162
- 'title',
16163
- 'value',
16164
- 'style',
16165
- 'xmlns'
16166
- ]);
15789
+ const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
16167
15790
  const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
16168
15791
  const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
16169
15792
  const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
15793
+ /* Document namespace */
16170
15794
  let NAMESPACE = HTML_NAMESPACE;
16171
15795
  let IS_EMPTY_INPUT = false;
15796
+
15797
+ /* Allowed XHTML+XML namespaces */
16172
15798
  let ALLOWED_NAMESPACES = null;
16173
- const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [
16174
- MATHML_NAMESPACE,
16175
- SVG_NAMESPACE,
16176
- HTML_NAMESPACE
16177
- ], stringToString);
16178
- let PARSER_MEDIA_TYPE;
16179
- const SUPPORTED_PARSER_MEDIA_TYPES = [
16180
- 'application/xhtml+xml',
16181
- 'text/html'
16182
- ];
15799
+ const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
15800
+
15801
+ /* Parsing of strict XHTML documents */
15802
+ let PARSER_MEDIA_TYPE = null;
15803
+ const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
16183
15804
  const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
16184
- let transformCaseFunc;
15805
+ let transformCaseFunc = null;
15806
+
15807
+ /* Keep a reference to config to pass to hooks */
16185
15808
  let CONFIG = null;
15809
+
15810
+ /* Ideally, do not touch anything below this line */
15811
+ /* ______________________________________________ */
15812
+
16186
15813
  const formElement = document.createElement('form');
16187
15814
  const isRegexOrFunction = function isRegexOrFunction(testValue) {
16188
15815
  return testValue instanceof RegExp || testValue instanceof Function;
16189
15816
  };
16190
- const _parseConfig = function _parseConfig(cfg) {
15817
+
15818
+ /**
15819
+ * _parseConfig
15820
+ *
15821
+ * @param {Object} cfg optional config literal
15822
+ */
15823
+ // eslint-disable-next-line complexity
15824
+ const _parseConfig = function _parseConfig() {
15825
+ let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
16191
15826
  if (CONFIG && CONFIG === cfg) {
16192
15827
  return;
16193
15828
  }
15829
+
15830
+ /* Shield configuration object from tampering */
16194
15831
  if (!cfg || typeof cfg !== 'object') {
16195
15832
  cfg = {};
16196
15833
  }
15834
+
15835
+ /* Shield configuration object from prototype pollution */
16197
15836
  cfg = clone(cfg);
16198
- PARSER_MEDIA_TYPE = SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE : PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE;
15837
+ PARSER_MEDIA_TYPE =
15838
+ // eslint-disable-next-line unicorn/prefer-includes
15839
+ SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
15840
+
15841
+ // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
16199
15842
  transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
16200
- ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
16201
- ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
16202
- ALLOWED_NAMESPACES = 'ALLOWED_NAMESPACES' in cfg ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
16203
- URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
16204
- DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
16205
- FORBID_CONTENTS = 'FORBID_CONTENTS' in cfg ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
16206
- FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {};
16207
- FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {};
16208
- USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
16209
- ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false;
16210
- ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false;
16211
- ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false;
16212
- ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false;
16213
- SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false;
16214
- WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false;
16215
- RETURN_DOM = cfg.RETURN_DOM || false;
16216
- RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false;
16217
- RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false;
16218
- FORCE_BODY = cfg.FORCE_BODY || false;
16219
- SANITIZE_DOM = cfg.SANITIZE_DOM !== false;
16220
- SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false;
16221
- KEEP_CONTENT = cfg.KEEP_CONTENT !== false;
16222
- IN_PLACE = cfg.IN_PLACE || false;
15843
+
15844
+ /* Set configuration parameters */
15845
+ ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
15846
+ ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
15847
+ ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
15848
+ URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES),
15849
+ // eslint-disable-line indent
15850
+ cfg.ADD_URI_SAFE_ATTR,
15851
+ // eslint-disable-line indent
15852
+ transformCaseFunc // eslint-disable-line indent
15853
+ ) // eslint-disable-line indent
15854
+ : DEFAULT_URI_SAFE_ATTRIBUTES;
15855
+ DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS),
15856
+ // eslint-disable-line indent
15857
+ cfg.ADD_DATA_URI_TAGS,
15858
+ // eslint-disable-line indent
15859
+ transformCaseFunc // eslint-disable-line indent
15860
+ ) // eslint-disable-line indent
15861
+ : DEFAULT_DATA_URI_TAGS;
15862
+ FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
15863
+ FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {};
15864
+ FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {};
15865
+ USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
15866
+ ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
15867
+ ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
15868
+ ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
15869
+ ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
15870
+ SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
15871
+ SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
15872
+ WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
15873
+ RETURN_DOM = cfg.RETURN_DOM || false; // Default false
15874
+ RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
15875
+ RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
15876
+ FORCE_BODY = cfg.FORCE_BODY || false; // Default false
15877
+ SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
15878
+ SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
15879
+ KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
15880
+ IN_PLACE = cfg.IN_PLACE || false; // Default false
16223
15881
  IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
16224
15882
  NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
16225
15883
  CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
@@ -16238,8 +15896,10 @@
16238
15896
  if (RETURN_DOM_FRAGMENT) {
16239
15897
  RETURN_DOM = true;
16240
15898
  }
15899
+
15900
+ /* Parse profile info */
16241
15901
  if (USE_PROFILES) {
16242
- ALLOWED_TAGS = addToSet({}, [...text]);
15902
+ ALLOWED_TAGS = addToSet({}, text);
16243
15903
  ALLOWED_ATTR = [];
16244
15904
  if (USE_PROFILES.html === true) {
16245
15905
  addToSet(ALLOWED_TAGS, html$1);
@@ -16261,6 +15921,8 @@
16261
15921
  addToSet(ALLOWED_ATTR, xml);
16262
15922
  }
16263
15923
  }
15924
+
15925
+ /* Merge configuration parameters */
16264
15926
  if (cfg.ADD_TAGS) {
16265
15927
  if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
16266
15928
  ALLOWED_TAGS = clone(ALLOWED_TAGS);
@@ -16282,16 +15944,18 @@
16282
15944
  }
16283
15945
  addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
16284
15946
  }
15947
+
15948
+ /* Add #text in case KEEP_CONTENT is set to true */
16285
15949
  if (KEEP_CONTENT) {
16286
15950
  ALLOWED_TAGS['#text'] = true;
16287
15951
  }
15952
+
15953
+ /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
16288
15954
  if (WHOLE_DOCUMENT) {
16289
- addToSet(ALLOWED_TAGS, [
16290
- 'html',
16291
- 'head',
16292
- 'body'
16293
- ]);
15955
+ addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
16294
15956
  }
15957
+
15958
+ /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
16295
15959
  if (ALLOWED_TAGS.table) {
16296
15960
  addToSet(ALLOWED_TAGS, ['tbody']);
16297
15961
  delete FORBID_TAGS.tbody;
@@ -16303,48 +15967,57 @@
16303
15967
  if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
16304
15968
  throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
16305
15969
  }
15970
+
15971
+ // Overwrite existing TrustedTypes policy.
16306
15972
  trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
15973
+
15974
+ // Sign local variables required by `sanitize`.
16307
15975
  emptyHTML = trustedTypesPolicy.createHTML('');
16308
15976
  } else {
15977
+ // Uninitialized policy, attempt to initialize the internal dompurify policy.
16309
15978
  if (trustedTypesPolicy === undefined) {
16310
15979
  trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
16311
15980
  }
15981
+
15982
+ // If creating the internal policy succeeded sign internal variables.
16312
15983
  if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
16313
15984
  emptyHTML = trustedTypesPolicy.createHTML('');
16314
15985
  }
16315
15986
  }
15987
+
15988
+ // Prevent further manipulation of configuration.
15989
+ // Not available in IE8, Safari 5, etc.
16316
15990
  if (freeze) {
16317
15991
  freeze(cfg);
16318
15992
  }
16319
15993
  CONFIG = cfg;
16320
15994
  };
16321
- const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, [
16322
- 'mi',
16323
- 'mo',
16324
- 'mn',
16325
- 'ms',
16326
- 'mtext'
16327
- ]);
16328
- const HTML_INTEGRATION_POINTS = addToSet({}, [
16329
- 'foreignobject',
16330
- 'desc',
16331
- 'title',
16332
- 'annotation-xml'
16333
- ]);
16334
- const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, [
16335
- 'title',
16336
- 'style',
16337
- 'font',
16338
- 'a',
16339
- 'script'
16340
- ]);
16341
- const ALL_SVG_TAGS = addToSet({}, svg$1);
16342
- addToSet(ALL_SVG_TAGS, svgFilters);
16343
- addToSet(ALL_SVG_TAGS, svgDisallowed);
16344
- const ALL_MATHML_TAGS = addToSet({}, mathMl$1);
16345
- addToSet(ALL_MATHML_TAGS, mathMlDisallowed);
15995
+ const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
15996
+ const HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);
15997
+
15998
+ // Certain elements are allowed in both SVG and HTML
15999
+ // namespace. We need to specify them explicitly
16000
+ // so that they don't get erroneously deleted from
16001
+ // HTML namespace.
16002
+ const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
16003
+
16004
+ /* Keep track of all possible SVG and MathML tags
16005
+ * so that we can perform the namespace checks
16006
+ * correctly. */
16007
+ const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
16008
+ const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
16009
+
16010
+ /**
16011
+ * @param {Element} element a DOM element whose namespace is being checked
16012
+ * @returns {boolean} Return false if the element has a
16013
+ * namespace that a spec-compliant parser would never
16014
+ * return. Return true otherwise.
16015
+ */
16346
16016
  const _checkValidNamespace = function _checkValidNamespace(element) {
16347
16017
  let parent = getParentNode(element);
16018
+
16019
+ // In JSDOM, if we're inside shadow DOM, then parentNode
16020
+ // can be null. We just simulate parent in this case.
16348
16021
  if (!parent || !parent.tagName) {
16349
16022
  parent = {
16350
16023
  namespaceURI: NAMESPACE,
@@ -16357,45 +16030,93 @@
16357
16030
  return false;
16358
16031
  }
16359
16032
  if (element.namespaceURI === SVG_NAMESPACE) {
16033
+ // The only way to switch from HTML namespace to SVG
16034
+ // is via <svg>. If it happens via any other tag, then
16035
+ // it should be killed.
16360
16036
  if (parent.namespaceURI === HTML_NAMESPACE) {
16361
16037
  return tagName === 'svg';
16362
16038
  }
16039
+
16040
+ // The only way to switch from MathML to SVG is via`
16041
+ // svg if parent is either <annotation-xml> or MathML
16042
+ // text integration points.
16363
16043
  if (parent.namespaceURI === MATHML_NAMESPACE) {
16364
16044
  return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
16365
16045
  }
16046
+
16047
+ // We only allow elements that are defined in SVG
16048
+ // spec. All others are disallowed in SVG namespace.
16366
16049
  return Boolean(ALL_SVG_TAGS[tagName]);
16367
16050
  }
16368
16051
  if (element.namespaceURI === MATHML_NAMESPACE) {
16052
+ // The only way to switch from HTML namespace to MathML
16053
+ // is via <math>. If it happens via any other tag, then
16054
+ // it should be killed.
16369
16055
  if (parent.namespaceURI === HTML_NAMESPACE) {
16370
16056
  return tagName === 'math';
16371
16057
  }
16058
+
16059
+ // The only way to switch from SVG to MathML is via
16060
+ // <math> and HTML integration points
16372
16061
  if (parent.namespaceURI === SVG_NAMESPACE) {
16373
16062
  return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
16374
16063
  }
16064
+
16065
+ // We only allow elements that are defined in MathML
16066
+ // spec. All others are disallowed in MathML namespace.
16375
16067
  return Boolean(ALL_MATHML_TAGS[tagName]);
16376
16068
  }
16377
16069
  if (element.namespaceURI === HTML_NAMESPACE) {
16070
+ // The only way to switch from SVG to HTML is via
16071
+ // HTML integration points, and from MathML to HTML
16072
+ // is via MathML text integration points
16378
16073
  if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
16379
16074
  return false;
16380
16075
  }
16381
16076
  if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
16382
16077
  return false;
16383
16078
  }
16079
+
16080
+ // We disallow tags that are specific for MathML
16081
+ // or SVG and should never appear in HTML namespace
16384
16082
  return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
16385
16083
  }
16084
+
16085
+ // For XHTML and XML documents that support custom namespaces
16386
16086
  if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
16387
16087
  return true;
16388
16088
  }
16089
+
16090
+ // The code should never reach this place (this means
16091
+ // that the element somehow got namespace that is not
16092
+ // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
16093
+ // Return false just in case.
16389
16094
  return false;
16390
16095
  };
16096
+
16097
+ /**
16098
+ * _forceRemove
16099
+ *
16100
+ * @param {Node} node a DOM node
16101
+ */
16391
16102
  const _forceRemove = function _forceRemove(node) {
16392
- arrayPush(DOMPurify.removed, { element: node });
16103
+ arrayPush(DOMPurify.removed, {
16104
+ element: node
16105
+ });
16393
16106
  try {
16394
- node.parentNode.removeChild(node);
16107
+ // eslint-disable-next-line unicorn/prefer-dom-node-remove
16108
+ getParentNode(node).removeChild(node);
16395
16109
  } catch (_) {
16396
- node.remove();
16110
+ remove(node);
16397
16111
  }
16398
16112
  };
16113
+
16114
+ /**
16115
+ * _removeAttribute
16116
+ *
16117
+ * @param {String} name an Attribute name
16118
+ * @param {Node} node a DOM node
16119
+ */
16399
16120
  const _removeAttribute = function _removeAttribute(name, node) {
16400
16121
  try {
16401
16122
  arrayPush(DOMPurify.removed, {
@@ -16409,64 +16130,114 @@
16409
16130
  });
16410
16131
  }
16411
16132
  node.removeAttribute(name);
16133
+
16134
+ // We void attribute values for unremovable "is"" attributes
16412
16135
  if (name === 'is' && !ALLOWED_ATTR[name]) {
16413
16136
  if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
16414
16137
  try {
16415
16138
  _forceRemove(node);
16416
- } catch (_) {
16417
- }
16139
+ } catch (_) {}
16418
16140
  } else {
16419
16141
  try {
16420
16142
  node.setAttribute(name, '');
16421
- } catch (_) {
16422
- }
16143
+ } catch (_) {}
16423
16144
  }
16424
16145
  }
16425
16146
  };
16147
+
16148
+ /**
16149
+ * _initDocument
16150
+ *
16151
+ * @param {String} dirty a string of dirty markup
16152
+ * @return {Document} a DOM, filled with the dirty markup
16153
+ */
16426
16154
  const _initDocument = function _initDocument(dirty) {
16427
- let doc;
16428
- let leadingWhitespace;
16155
+ /* Create a HTML document */
16156
+ let doc = null;
16157
+ let leadingWhitespace = null;
16429
16158
  if (FORCE_BODY) {
16430
16159
  dirty = '<remove></remove>' + dirty;
16431
16160
  } else {
16161
+ /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
16432
16162
  const matches = stringMatch(dirty, /^[\r\n\t ]+/);
16433
16163
  leadingWhitespace = matches && matches[0];
16434
16164
  }
16435
16165
  if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {
16166
+ // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
16436
16167
  dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
16437
16168
  }
16438
16169
  const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
16170
+ /*
16171
+ * Use the DOMParser API by default, fallback later if needs be
16172
+ * DOMParser not work for svg when has multiple root element.
16173
+ */
16439
16174
  if (NAMESPACE === HTML_NAMESPACE) {
16440
16175
  try {
16441
16176
  doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
16442
- } catch (_) {
16443
- }
16177
+ } catch (_) {}
16444
16178
  }
16179
+
16180
+ /* Use createHTMLDocument in case DOMParser is not available */
16445
16181
  if (!doc || !doc.documentElement) {
16446
16182
  doc = implementation.createDocument(NAMESPACE, 'template', null);
16447
16183
  try {
16448
16184
  doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
16449
16185
  } catch (_) {
16186
+ // Syntax error if dirtyPayload is invalid xml
16450
16187
  }
16451
16188
  }
16452
16189
  const body = doc.body || doc.documentElement;
16453
16190
  if (dirty && leadingWhitespace) {
16454
16191
  body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
16455
16192
  }
16193
+
16194
+ /* Work on whole document or just its body */
16456
16195
  if (NAMESPACE === HTML_NAMESPACE) {
16457
16196
  return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
16458
16197
  }
16459
16198
  return WHOLE_DOCUMENT ? doc.documentElement : body;
16460
16199
  };
16461
- const _createIterator = function _createIterator(root) {
16462
- return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, null, false);
16463
- };
16200
+
16201
+ /**
16202
+ * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
16203
+ *
16204
+ * @param {Node} root The root element or node to start traversing on.
16205
+ * @return {NodeIterator} The created NodeIterator
16206
+ */
16207
+ const _createNodeIterator = function _createNodeIterator(root) {
16208
+ return createNodeIterator.call(root.ownerDocument || root, root,
16209
+ // eslint-disable-next-line no-bitwise
16210
+ NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
16211
+ };
16212
+
16213
+ /**
16214
+ * _isClobbered
16215
+ *
16216
+ * @param {Node} elm element to check for clobbering attacks
16217
+ * @return {Boolean} true if clobbered, false if safe
16218
+ */
16464
16219
  const _isClobbered = function _isClobbered(elm) {
16465
16220
  return elm instanceof HTMLFormElement && (typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function' || typeof elm.hasChildNodes !== 'function');
16466
16221
  };
16222
+
16223
+ /**
16224
+ * Checks whether the given object is a DOM node.
16225
+ *
16226
+ * @param {Node} object object to check whether it's a DOM node
16227
+ * @return {Boolean} true is object is a DOM node
16228
+ */
16467
16229
  const _isNode = function _isNode(object) {
16468
- return typeof Node === 'object' ? object instanceof Node : object && typeof object === 'object' && typeof object.nodeType === 'number' && typeof object.nodeName === 'string';
16230
+ return typeof Node === 'function' && object instanceof Node;
16469
16231
  };
16232
+
16233
+ /**
16234
+ * _executeHook
16235
+ * Execute user configurable hooks
16236
+ *
16237
+ * @param {String} entryPoint Name of the hook's entry point
16238
+ * @param {Node} currentNode node to work on with the hook
16239
+ * @param {Object} data additional hook parameters
16240
+ */
16470
16241
  const _executeHook = function _executeHook(entryPoint, currentNode, data) {
16471
16242
  if (!hooks[entryPoint]) {
16472
16243
  return;
@@ -16475,93 +16246,184 @@
16475
16246
  hook.call(DOMPurify, currentNode, data, CONFIG);
16476
16247
  });
16477
16248
  };
16249
+
16250
+ /**
16251
+ * _sanitizeElements
16252
+ *
16253
+ * @protect nodeName
16254
+ * @protect textContent
16255
+ * @protect removeChild
16256
+ *
16257
+ * @param {Node} currentNode to check for permission to exist
16258
+ * @return {Boolean} true if node was killed, false if left alive
16259
+ */
16478
16260
  const _sanitizeElements = function _sanitizeElements(currentNode) {
16479
- let content;
16261
+ let content = null;
16262
+
16263
+ /* Execute a hook if present */
16480
16264
  _executeHook('beforeSanitizeElements', currentNode, null);
16265
+
16266
+ /* Check if element is clobbered or can clobber */
16481
16267
  if (_isClobbered(currentNode)) {
16482
16268
  _forceRemove(currentNode);
16483
16269
  return true;
16484
16270
  }
16271
+
16272
+ /* Now let's check the element's type and name */
16485
16273
  const tagName = transformCaseFunc(currentNode.nodeName);
16274
+
16275
+ /* Execute a hook if present */
16486
16276
  _executeHook('uponSanitizeElement', currentNode, {
16487
16277
  tagName,
16488
16278
  allowedTags: ALLOWED_TAGS
16489
16279
  });
16490
- if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
16280
+
16281
+ /* Detect mXSS attempts abusing namespace confusion */
16282
+ if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
16283
+ _forceRemove(currentNode);
16284
+ return true;
16285
+ }
16286
+
16287
+ /* Remove any occurrence of processing instructions */
16288
+ if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
16491
16289
  _forceRemove(currentNode);
16492
16290
  return true;
16493
16291
  }
16292
+
16293
+ /* Remove any kind of possibly harmful comments */
16294
+ if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
16295
+ _forceRemove(currentNode);
16296
+ return true;
16297
+ }
16298
+
16299
+ /* Remove element if anything forbids its presence */
16494
16300
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
16495
- if (!FORBID_TAGS[tagName] && _basicCustomElementTest(tagName)) {
16496
- if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName))
16301
+ /* Check if we have a custom element to handle */
16302
+ if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
16303
+ if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
16497
16304
  return false;
16498
- if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName))
16305
+ }
16306
+ if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
16499
16307
  return false;
16308
+ }
16500
16309
  }
16310
+
16311
+ /* Keep content except for bad-listed elements */
16501
16312
  if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
16502
16313
  const parentNode = getParentNode(currentNode) || currentNode.parentNode;
16503
16314
  const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
16504
16315
  if (childNodes && parentNode) {
16505
16316
  const childCount = childNodes.length;
16506
16317
  for (let i = childCount - 1; i >= 0; --i) {
16507
- parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
16318
+ const childClone = cloneNode(childNodes[i], true);
16319
+ childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
16320
+ parentNode.insertBefore(childClone, getNextSibling(currentNode));
16508
16321
  }
16509
16322
  }
16510
16323
  }
16511
16324
  _forceRemove(currentNode);
16512
16325
  return true;
16513
16326
  }
16327
+
16328
+ /* Check whether element has a valid namespace */
16514
16329
  if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
16515
16330
  _forceRemove(currentNode);
16516
16331
  return true;
16517
16332
  }
16333
+
16334
+ /* Make sure that older browsers don't get fallback-tag mXSS */
16518
16335
  if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
16519
16336
  _forceRemove(currentNode);
16520
16337
  return true;
16521
16338
  }
16522
- if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
16339
+
16340
+ /* Sanitize element content to be template-safe */
16341
+ if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
16342
+ /* Get the element's text content */
16523
16343
  content = currentNode.textContent;
16524
- content = stringReplace(content, MUSTACHE_EXPR, ' ');
16525
- content = stringReplace(content, ERB_EXPR, ' ');
16526
- content = stringReplace(content, TMPLIT_EXPR, ' ');
16344
+ arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
16345
+ content = stringReplace(content, expr, ' ');
16346
+ });
16527
16347
  if (currentNode.textContent !== content) {
16528
- arrayPush(DOMPurify.removed, { element: currentNode.cloneNode() });
16348
+ arrayPush(DOMPurify.removed, {
16349
+ element: currentNode.cloneNode()
16350
+ });
16529
16351
  currentNode.textContent = content;
16530
16352
  }
16531
16353
  }
16354
+
16355
+ /* Execute a hook if present */
16532
16356
  _executeHook('afterSanitizeElements', currentNode, null);
16533
16357
  return false;
16534
16358
  };
16359
+
16360
+ /**
16361
+ * _isValidAttribute
16362
+ *
16363
+ * @param {string} lcTag Lowercase tag name of containing element.
16364
+ * @param {string} lcName Lowercase attribute name.
16365
+ * @param {string} value Attribute value.
16366
+ * @return {Boolean} Returns true if `value` is valid, otherwise false.
16367
+ */
16368
+ // eslint-disable-next-line complexity
16535
16369
  const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
16370
+ /* Make sure attribute cannot clobber */
16536
16371
  if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
16537
16372
  return false;
16538
16373
  }
16539
- if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName));
16540
- else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName));
16541
- else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
16542
- if (_basicCustomElementTest(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)) || 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)));
16543
- else {
16374
+
16375
+ /* Allow valid data-* attributes: At least one character after "-"
16376
+ (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
16377
+ XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
16378
+ We don't need to check the value; it's always URI safe. */
16379
+ if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
16380
+ if (
16381
+ // First condition does a very basic check if a) it's basically a valid custom element tagname AND
16382
+ // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
16383
+ // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
16384
+ _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)) ||
16385
+ // Alternative, second condition checks if it's an `is`-attribute, AND
16386
+ // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
16387
+ 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))) ; else {
16544
16388
  return false;
16545
16389
  }
16546
- } else if (URI_SAFE_ATTRIBUTES[lcName]);
16547
- else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, '')));
16548
- else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]);
16549
- else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, '')));
16550
- else if (value) {
16390
+ /* Check value is safe. First, is attr inert? If so, is safe */
16391
+ } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) {
16551
16392
  return false;
16552
16393
  } else ;
16553
16394
  return true;
16554
16395
  };
16555
- const _basicCustomElementTest = function _basicCustomElementTest(tagName) {
16556
- return tagName.indexOf('-') > 0;
16557
- };
16396
+
16397
+ /**
16398
+ * _isBasicCustomElement
16399
+ * checks if at least one dash is included in tagName, and it's not the first char
16400
+ * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
16401
+ *
16402
+ * @param {string} tagName name of the tag of the node to sanitize
16403
+ * @returns {boolean} Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
16404
+ */
16405
+ const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
16406
+ return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
16407
+ };
16408
+
16409
+ /**
16410
+ * _sanitizeAttributes
16411
+ *
16412
+ * @protect attributes
16413
+ * @protect nodeName
16414
+ * @protect removeAttribute
16415
+ * @protect setAttribute
16416
+ *
16417
+ * @param {Node} currentNode to sanitize
16418
+ */
16558
16419
  const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
16559
- let attr;
16560
- let value;
16561
- let lcName;
16562
- let l;
16420
+ /* Execute a hook if present */
16563
16421
  _executeHook('beforeSanitizeAttributes', currentNode, null);
16564
- const {attributes} = currentNode;
16422
+ const {
16423
+ attributes
16424
+ } = currentNode;
16425
+
16426
+ /* Check if we have attributes; if not we might have a text node */
16565
16427
  if (!attributes) {
16566
16428
  return;
16567
16429
  }
@@ -16571,99 +16433,174 @@
16571
16433
  keepAttr: true,
16572
16434
  allowedAttributes: ALLOWED_ATTR
16573
16435
  };
16574
- l = attributes.length;
16436
+ let l = attributes.length;
16437
+
16438
+ /* Go backwards over all attributes; safely remove bad ones */
16575
16439
  while (l--) {
16576
- attr = attributes[l];
16577
- const {name, namespaceURI} = attr;
16578
- value = name === 'value' ? attr.value : stringTrim(attr.value);
16440
+ const attr = attributes[l];
16441
+ const {
16442
+ name,
16443
+ namespaceURI,
16444
+ value: attrValue
16445
+ } = attr;
16446
+ const lcName = transformCaseFunc(name);
16447
+ let value = name === 'value' ? attrValue : stringTrim(attrValue);
16579
16448
  const initValue = value;
16580
- lcName = transformCaseFunc(name);
16449
+
16450
+ /* Execute a hook if present */
16581
16451
  hookEvent.attrName = lcName;
16582
16452
  hookEvent.attrValue = value;
16583
16453
  hookEvent.keepAttr = true;
16584
- hookEvent.forceKeepAttr = undefined;
16454
+ hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
16585
16455
  _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
16586
16456
  value = hookEvent.attrValue;
16457
+
16458
+ /* Did the hooks approve of the attribute? */
16587
16459
  if (hookEvent.forceKeepAttr) {
16588
16460
  continue;
16589
16461
  }
16462
+
16463
+ /* Remove attribute */
16464
+
16465
+ /* Did the hooks approve of the attribute? */
16590
16466
  if (!hookEvent.keepAttr) {
16591
16467
  _removeAttribute(name, currentNode);
16592
16468
  continue;
16593
16469
  }
16470
+
16471
+ /* Work around a security issue in jQuery 3.0 */
16594
16472
  if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
16595
16473
  _removeAttribute(name, currentNode);
16596
16474
  continue;
16597
16475
  }
16476
+
16477
+ /* Sanitize attribute content to be template-safe */
16598
16478
  if (SAFE_FOR_TEMPLATES) {
16599
- value = stringReplace(value, MUSTACHE_EXPR, ' ');
16600
- value = stringReplace(value, ERB_EXPR, ' ');
16601
- value = stringReplace(value, TMPLIT_EXPR, ' ');
16479
+ arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
16480
+ value = stringReplace(value, expr, ' ');
16481
+ });
16602
16482
  }
16483
+
16484
+ /* Is `value` valid for this attribute? */
16603
16485
  const lcTag = transformCaseFunc(currentNode.nodeName);
16604
16486
  if (!_isValidAttribute(lcTag, lcName, value)) {
16605
16487
  _removeAttribute(name, currentNode);
16606
16488
  continue;
16607
16489
  }
16490
+
16491
+ /* Full DOM Clobbering protection via namespace isolation,
16492
+ * Prefix id and name attributes with `user-content-`
16493
+ */
16608
16494
  if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
16495
+ // Remove the attribute with this value
16609
16496
  _removeAttribute(name, currentNode);
16497
+
16498
+ // Prefix the value and later re-create the attribute with the sanitized value
16610
16499
  value = SANITIZE_NAMED_PROPS_PREFIX + value;
16611
16500
  }
16501
+
16502
+ /* Work around a security issue with comments inside attributes */
16503
+ if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) {
16504
+ _removeAttribute(name, currentNode);
16505
+ continue;
16506
+ }
16507
+
16508
+ /* Handle attributes that require Trusted Types */
16612
16509
  if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
16613
- if (namespaceURI);
16614
- else {
16510
+ if (namespaceURI) ; else {
16615
16511
  switch (trustedTypes.getAttributeType(lcTag, lcName)) {
16616
- case 'TrustedHTML': {
16617
- value = trustedTypesPolicy.createHTML(value);
16618
- break;
16619
- }
16620
- case 'TrustedScriptURL': {
16621
- value = trustedTypesPolicy.createScriptURL(value);
16622
- break;
16623
- }
16512
+ case 'TrustedHTML':
16513
+ {
16514
+ value = trustedTypesPolicy.createHTML(value);
16515
+ break;
16516
+ }
16517
+ case 'TrustedScriptURL':
16518
+ {
16519
+ value = trustedTypesPolicy.createScriptURL(value);
16520
+ break;
16521
+ }
16624
16522
  }
16625
16523
  }
16626
16524
  }
16525
+
16526
+ /* Handle invalid data-* attribute set by try-catching it */
16627
16527
  if (value !== initValue) {
16628
16528
  try {
16629
16529
  if (namespaceURI) {
16630
16530
  currentNode.setAttributeNS(namespaceURI, name, value);
16631
16531
  } else {
16532
+ /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
16632
16533
  currentNode.setAttribute(name, value);
16633
16534
  }
16634
- } catch (_) {
16635
- _removeAttribute(name, currentNode);
16636
- }
16535
+ if (_isClobbered(currentNode)) {
16536
+ _forceRemove(currentNode);
16537
+ } else {
16538
+ arrayPop(DOMPurify.removed);
16539
+ }
16540
+ } catch (_) {}
16637
16541
  }
16638
16542
  }
16543
+
16544
+ /* Execute a hook if present */
16639
16545
  _executeHook('afterSanitizeAttributes', currentNode, null);
16640
16546
  };
16547
+
16548
+ /**
16549
+ * _sanitizeShadowDOM
16550
+ *
16551
+ * @param {DocumentFragment} fragment to iterate over recursively
16552
+ */
16641
16553
  const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
16642
- let shadowNode;
16643
- const shadowIterator = _createIterator(fragment);
16554
+ let shadowNode = null;
16555
+ const shadowIterator = _createNodeIterator(fragment);
16556
+
16557
+ /* Execute a hook if present */
16644
16558
  _executeHook('beforeSanitizeShadowDOM', fragment, null);
16645
16559
  while (shadowNode = shadowIterator.nextNode()) {
16560
+ /* Execute a hook if present */
16646
16561
  _executeHook('uponSanitizeShadowNode', shadowNode, null);
16562
+
16563
+ /* Sanitize tags and elements */
16647
16564
  if (_sanitizeElements(shadowNode)) {
16648
16565
  continue;
16649
16566
  }
16567
+
16568
+ /* Deep shadow DOM detected */
16650
16569
  if (shadowNode.content instanceof DocumentFragment) {
16651
16570
  _sanitizeShadowDOM(shadowNode.content);
16652
16571
  }
16572
+
16573
+ /* Check attributes, sanitize if necessary */
16653
16574
  _sanitizeAttributes(shadowNode);
16654
16575
  }
16576
+
16577
+ /* Execute a hook if present */
16655
16578
  _executeHook('afterSanitizeShadowDOM', fragment, null);
16656
16579
  };
16580
+
16581
+ /**
16582
+ * Sanitize
16583
+ * Public method providing core sanitation functionality
16584
+ *
16585
+ * @param {String|Node} dirty string or DOM node
16586
+ * @param {Object} cfg object
16587
+ */
16588
+ // eslint-disable-next-line complexity
16657
16589
  DOMPurify.sanitize = function (dirty) {
16658
16590
  let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
16659
- let body;
16660
- let importedNode;
16661
- let currentNode;
16662
- let returnNode;
16591
+ let body = null;
16592
+ let importedNode = null;
16593
+ let currentNode = null;
16594
+ let returnNode = null;
16595
+ /* Make sure we have a string to sanitize.
16596
+ DO NOT return early, as this will return the wrong type if
16597
+ the user has requested a DOM object rather than a string */
16663
16598
  IS_EMPTY_INPUT = !dirty;
16664
16599
  if (IS_EMPTY_INPUT) {
16665
16600
  dirty = '<!-->';
16666
16601
  }
16602
+
16603
+ /* Stringify, in case dirty is an object */
16667
16604
  if (typeof dirty !== 'string' && !_isNode(dirty)) {
16668
16605
  if (typeof dirty.toString === 'function') {
16669
16606
  dirty = dirty.toString();
@@ -16674,17 +16611,26 @@
16674
16611
  throw typeErrorCreate('toString is not a function');
16675
16612
  }
16676
16613
  }
16614
+
16615
+ /* Return dirty HTML if DOMPurify cannot run */
16677
16616
  if (!DOMPurify.isSupported) {
16678
16617
  return dirty;
16679
16618
  }
16619
+
16620
+ /* Assign config vars */
16680
16621
  if (!SET_CONFIG) {
16681
16622
  _parseConfig(cfg);
16682
16623
  }
16624
+
16625
+ /* Clean up removed elements */
16683
16626
  DOMPurify.removed = [];
16627
+
16628
+ /* Check if dirty is correctly typed for IN_PLACE */
16684
16629
  if (typeof dirty === 'string') {
16685
16630
  IN_PLACE = false;
16686
16631
  }
16687
16632
  if (IN_PLACE) {
16633
+ /* Do some early pre-sanitization to avoid unsafe root nodes */
16688
16634
  if (dirty.nodeName) {
16689
16635
  const tagName = transformCaseFunc(dirty.nodeName);
16690
16636
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
@@ -16692,74 +16638,138 @@
16692
16638
  }
16693
16639
  }
16694
16640
  } else if (dirty instanceof Node) {
16641
+ /* If dirty is a DOM element, append to an empty document to avoid
16642
+ elements being stripped by the parser */
16695
16643
  body = _initDocument('<!---->');
16696
16644
  importedNode = body.ownerDocument.importNode(dirty, true);
16697
- if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
16645
+ if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') {
16646
+ /* Node is already a body, use as is */
16698
16647
  body = importedNode;
16699
16648
  } else if (importedNode.nodeName === 'HTML') {
16700
16649
  body = importedNode;
16701
16650
  } else {
16651
+ // eslint-disable-next-line unicorn/prefer-dom-node-append
16702
16652
  body.appendChild(importedNode);
16703
16653
  }
16704
16654
  } else {
16705
- if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && dirty.indexOf('<') === -1) {
16655
+ /* Exit directly if we have nothing to do */
16656
+ if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
16657
+ // eslint-disable-next-line unicorn/prefer-includes
16658
+ dirty.indexOf('<') === -1) {
16706
16659
  return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
16707
16660
  }
16661
+
16662
+ /* Initialize the document to work on */
16708
16663
  body = _initDocument(dirty);
16664
+
16665
+ /* Check we have a DOM node from the data */
16709
16666
  if (!body) {
16710
16667
  return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
16711
16668
  }
16712
16669
  }
16670
+
16671
+ /* Remove first element node (ours) if FORCE_BODY is set */
16713
16672
  if (body && FORCE_BODY) {
16714
16673
  _forceRemove(body.firstChild);
16715
16674
  }
16716
- const nodeIterator = _createIterator(IN_PLACE ? dirty : body);
16675
+
16676
+ /* Get node iterator */
16677
+ const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
16678
+
16679
+ /* Now start iterating over the created document */
16717
16680
  while (currentNode = nodeIterator.nextNode()) {
16681
+ /* Sanitize tags and elements */
16718
16682
  if (_sanitizeElements(currentNode)) {
16719
16683
  continue;
16720
16684
  }
16685
+
16686
+ /* Shadow DOM detected, sanitize it */
16721
16687
  if (currentNode.content instanceof DocumentFragment) {
16722
16688
  _sanitizeShadowDOM(currentNode.content);
16723
16689
  }
16690
+
16691
+ /* Check attributes, sanitize if necessary */
16724
16692
  _sanitizeAttributes(currentNode);
16725
16693
  }
16694
+
16695
+ /* If we sanitized `dirty` in-place, return it. */
16726
16696
  if (IN_PLACE) {
16727
16697
  return dirty;
16728
16698
  }
16699
+
16700
+ /* Return sanitized string or DOM */
16729
16701
  if (RETURN_DOM) {
16730
16702
  if (RETURN_DOM_FRAGMENT) {
16731
16703
  returnNode = createDocumentFragment.call(body.ownerDocument);
16732
16704
  while (body.firstChild) {
16705
+ // eslint-disable-next-line unicorn/prefer-dom-node-append
16733
16706
  returnNode.appendChild(body.firstChild);
16734
16707
  }
16735
16708
  } else {
16736
16709
  returnNode = body;
16737
16710
  }
16738
16711
  if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {
16712
+ /*
16713
+ AdoptNode() is not used because internal state is not reset
16714
+ (e.g. the past names map of a HTMLFormElement), this is safe
16715
+ in theory but we would rather not risk another attack vector.
16716
+ The state that is cloned by importNode() is explicitly defined
16717
+ by the specs.
16718
+ */
16739
16719
  returnNode = importNode.call(originalDocument, returnNode, true);
16740
16720
  }
16741
16721
  return returnNode;
16742
16722
  }
16743
16723
  let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
16724
+
16725
+ /* Serialize doctype if allowed */
16744
16726
  if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
16745
16727
  serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
16746
16728
  }
16729
+
16730
+ /* Sanitize final string template-safe */
16747
16731
  if (SAFE_FOR_TEMPLATES) {
16748
- serializedHTML = stringReplace(serializedHTML, MUSTACHE_EXPR, ' ');
16749
- serializedHTML = stringReplace(serializedHTML, ERB_EXPR, ' ');
16750
- serializedHTML = stringReplace(serializedHTML, TMPLIT_EXPR, ' ');
16732
+ arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
16733
+ serializedHTML = stringReplace(serializedHTML, expr, ' ');
16734
+ });
16751
16735
  }
16752
16736
  return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
16753
16737
  };
16754
- DOMPurify.setConfig = function (cfg) {
16738
+
16739
+ /**
16740
+ * Public method to set the configuration once
16741
+ * setConfig
16742
+ *
16743
+ * @param {Object} cfg configuration object
16744
+ */
16745
+ DOMPurify.setConfig = function () {
16746
+ let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
16755
16747
  _parseConfig(cfg);
16756
16748
  SET_CONFIG = true;
16757
16749
  };
16750
+
16751
+ /**
16752
+ * Public method to remove the configuration
16753
+ * clearConfig
16754
+ *
16755
+ */
16758
16756
  DOMPurify.clearConfig = function () {
16759
16757
  CONFIG = null;
16760
16758
  SET_CONFIG = false;
16761
16759
  };
16760
+
16761
+ /**
16762
+ * Public method to check if an attribute value is valid.
16763
+ * Uses last set config, if any. Otherwise, uses config defaults.
16764
+ * isValidAttribute
16765
+ *
16766
+ * @param {String} tag Tag name of containing element.
16767
+ * @param {String} attr Attribute name.
16768
+ * @param {String} value Attribute value.
16769
+ * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.
16770
+ */
16762
16771
  DOMPurify.isValidAttribute = function (tag, attr, value) {
16772
+ /* Initialize shared config vars if necessary. */
16763
16773
  if (!CONFIG) {
16764
16774
  _parseConfig({});
16765
16775
  }
@@ -16767,6 +16777,14 @@
16767
16777
  const lcName = transformCaseFunc(attr);
16768
16778
  return _isValidAttribute(lcTag, lcName, value);
16769
16779
  };
16780
+
16781
+ /**
16782
+ * AddHook
16783
+ * Public method to add DOMPurify hooks
16784
+ *
16785
+ * @param {String} entryPoint entry point for the hook to add
16786
+ * @param {Function} hookFunction function to execute
16787
+ */
16770
16788
  DOMPurify.addHook = function (entryPoint, hookFunction) {
16771
16789
  if (typeof hookFunction !== 'function') {
16772
16790
  return;
@@ -16774,16 +16792,37 @@
16774
16792
  hooks[entryPoint] = hooks[entryPoint] || [];
16775
16793
  arrayPush(hooks[entryPoint], hookFunction);
16776
16794
  };
16795
+
16796
+ /**
16797
+ * RemoveHook
16798
+ * Public method to remove a DOMPurify hook at a given entryPoint
16799
+ * (pops it from the stack of hooks if more are present)
16800
+ *
16801
+ * @param {String} entryPoint entry point for the hook to remove
16802
+ * @return {Function} removed(popped) hook
16803
+ */
16777
16804
  DOMPurify.removeHook = function (entryPoint) {
16778
16805
  if (hooks[entryPoint]) {
16779
16806
  return arrayPop(hooks[entryPoint]);
16780
16807
  }
16781
16808
  };
16809
+
16810
+ /**
16811
+ * RemoveHooks
16812
+ * Public method to remove all DOMPurify hooks at a given entryPoint
16813
+ *
16814
+ * @param {String} entryPoint entry point for the hooks to remove
16815
+ */
16782
16816
  DOMPurify.removeHooks = function (entryPoint) {
16783
16817
  if (hooks[entryPoint]) {
16784
16818
  hooks[entryPoint] = [];
16785
16819
  }
16786
16820
  };
16821
+
16822
+ /**
16823
+ * RemoveAllHooks
16824
+ * Public method to remove all DOMPurify hooks
16825
+ */
16787
16826
  DOMPurify.removeAllHooks = function () {
16788
16827
  hooks = {};
16789
16828
  };
@@ -17219,7 +17258,8 @@
17219
17258
  '#cdata-section',
17220
17259
  'body'
17221
17260
  ],
17222
- ALLOWED_ATTR: []
17261
+ ALLOWED_ATTR: [],
17262
+ SAFE_FOR_XML: false
17223
17263
  };
17224
17264
  const config = { ...basePurifyConfig };
17225
17265
  config.PARSER_MEDIA_TYPE = mimeType;
@@ -17230,37 +17270,55 @@
17230
17270
  }
17231
17271
  return config;
17232
17272
  };
17233
- const sanitizeNamespaceElement = ele => {
17273
+ const sanitizeSvgElement = ele => {
17274
+ const xlinkAttrs = [
17275
+ 'type',
17276
+ 'href',
17277
+ 'role',
17278
+ 'arcrole',
17279
+ 'title',
17280
+ 'show',
17281
+ 'actuate',
17282
+ 'label',
17283
+ 'from',
17284
+ 'to'
17285
+ ].map(name => `xlink:${ name }`);
17286
+ const config = {
17287
+ IN_PLACE: true,
17288
+ USE_PROFILES: {
17289
+ html: true,
17290
+ svg: true,
17291
+ svgFilters: true
17292
+ },
17293
+ ALLOWED_ATTR: xlinkAttrs
17294
+ };
17295
+ purify().sanitize(ele, config);
17296
+ };
17297
+ const sanitizeMathmlElement = (node, settings) => {
17298
+ const config = {
17299
+ IN_PLACE: true,
17300
+ USE_PROFILES: { mathMl: true }
17301
+ };
17302
+ const purify$1 = purify();
17303
+ purify$1.addHook('uponSanitizeElement', (node, evt) => {
17304
+ var _a;
17305
+ const lcTagName = (_a = evt.tagName) !== null && _a !== void 0 ? _a : node.nodeName.toLowerCase();
17306
+ const allowedEncodings = settings.allow_mathml_annotation_encodings;
17307
+ if (lcTagName === 'annotation' && isArray$1(allowedEncodings) && allowedEncodings.length > 0) {
17308
+ const encoding = node.getAttribute('encoding');
17309
+ if (isString(encoding) && contains$2(allowedEncodings, encoding)) {
17310
+ evt.allowedTags[lcTagName] = true;
17311
+ }
17312
+ }
17313
+ });
17314
+ purify$1.sanitize(node, config);
17315
+ };
17316
+ const mkSanitizeNamespaceElement = settings => ele => {
17234
17317
  const namespaceType = toScopeType(ele);
17235
17318
  if (namespaceType === 'svg') {
17236
- const xlinkAttrs = [
17237
- 'type',
17238
- 'href',
17239
- 'role',
17240
- 'arcrole',
17241
- 'title',
17242
- 'show',
17243
- 'actuate',
17244
- 'label',
17245
- 'from',
17246
- 'to'
17247
- ].map(name => `xlink:${ name }`);
17248
- const config = {
17249
- IN_PLACE: true,
17250
- USE_PROFILES: {
17251
- html: true,
17252
- svg: true,
17253
- svgFilters: true
17254
- },
17255
- ALLOWED_ATTR: xlinkAttrs
17256
- };
17257
- purify().sanitize(ele, config);
17319
+ sanitizeSvgElement(ele);
17258
17320
  } else if (namespaceType === 'math') {
17259
- const config = {
17260
- IN_PLACE: true,
17261
- USE_PROFILES: { mathMl: true }
17262
- };
17263
- purify().sanitize(ele, config);
17321
+ sanitizeMathmlElement(ele, settings);
17264
17322
  } else {
17265
17323
  throw new Error('Not a namespace element');
17266
17324
  }
@@ -17276,7 +17334,7 @@
17276
17334
  };
17277
17335
  return {
17278
17336
  sanitizeHtmlElement,
17279
- sanitizeNamespaceElement
17337
+ sanitizeNamespaceElement: mkSanitizeNamespaceElement(settings)
17280
17338
  };
17281
17339
  } else {
17282
17340
  const sanitizeHtmlElement = (body, _mimeType) => {
@@ -18743,6 +18801,9 @@
18743
18801
  return !sel || rng.collapsed;
18744
18802
  };
18745
18803
  const isEditable = () => {
18804
+ if (editor.mode.isReadOnly()) {
18805
+ return false;
18806
+ }
18746
18807
  const rng = getRng$1();
18747
18808
  const fakeSelectedElements = editor.getBody().querySelectorAll('[data-mce-selected="1"]');
18748
18809
  if (fakeSelectedElements.length > 0) {
@@ -22659,6 +22720,9 @@
22659
22720
  const getBlocksToIndent = editor => filter$5(fromDom$1(editor.selection.getSelectedBlocks()), el => !isListComponent(el) && !parentIsListComponent(el) && isEditable(el));
22660
22721
  const handle = (editor, command) => {
22661
22722
  var _a, _b;
22723
+ if (editor.mode.isReadOnly()) {
22724
+ return;
22725
+ }
22662
22726
  const {dom} = editor;
22663
22727
  const indentation = getIndentation(editor);
22664
22728
  const indentUnit = (_b = (_a = /[a-z%]+$/i.exec(indentation)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 'px';
@@ -23398,6 +23462,9 @@
23398
23462
  return getCellFirstCursorPosition(cell);
23399
23463
  });
23400
23464
  }, current => {
23465
+ if (editor.mode.isReadOnly()) {
23466
+ return Optional.none();
23467
+ }
23401
23468
  editor.execCommand('mceTableInsertRowAfter');
23402
23469
  return tabForward(editor, isRoot, current);
23403
23470
  });
@@ -24078,7 +24145,8 @@
24078
24145
  optionalTooltip,
24079
24146
  optionalIcon,
24080
24147
  optionalText,
24081
- onSetup
24148
+ onSetup,
24149
+ defaultedString('context', 'mode:design')
24082
24150
  ];
24083
24151
 
24084
24152
  const baseToolbarToggleButtonFields = [active].concat(baseToolbarButtonFields);
@@ -24941,9 +25009,12 @@
24941
25009
  const isEmptyAnchor = (dom, elm) => {
24942
25010
  return elm && elm.nodeName === 'A' && dom.isEmpty(elm);
24943
25011
  };
24944
- const containerAndSiblingName = (container, nodeName) => {
25012
+ const containerAndPreviousSiblingName = (container, nodeName) => {
24945
25013
  return container.nodeName === nodeName || container.previousSibling && container.previousSibling.nodeName === nodeName;
24946
25014
  };
25015
+ const containerAndNextSiblingName = (container, nodeName) => {
25016
+ return container.nodeName === nodeName || container.nextSibling && container.nextSibling.nodeName === nodeName;
25017
+ };
24947
25018
  const canSplitBlock = (dom, node) => {
24948
25019
  return isNonNullable(node) && dom.isBlock(node) && !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && !/^(fixed|absolute)/i.test(node.style.position) && dom.isEditable(node.parentNode) && dom.getContentEditable(node) !== 'false';
24949
25020
  };
@@ -25085,7 +25156,10 @@
25085
25156
  if (start && isElement$6(container) && container === parentBlock.firstChild) {
25086
25157
  return true;
25087
25158
  }
25088
- if (containerAndSiblingName(container, 'TABLE') || containerAndSiblingName(container, 'HR')) {
25159
+ if (containerAndPreviousSiblingName(container, 'TABLE') || containerAndPreviousSiblingName(container, 'HR')) {
25160
+ if (containerAndNextSiblingName(container, 'BR')) {
25161
+ return !start;
25162
+ }
25089
25163
  return isAfterLastNodeInContainer && !start || !isAfterLastNodeInContainer && start;
25090
25164
  }
25091
25165
  const walker = new DomTreeWalker(container, parentBlock);
@@ -25203,7 +25277,7 @@
25203
25277
  const afterBr = isAfterBr(parentBlockSugar, caretPos, editor.schema);
25204
25278
  const prevBrOpt = afterBr ? findPreviousBr(parentBlockSugar, caretPos, editor.schema).bind(pos => Optional.from(pos.getNode())) : Optional.none();
25205
25279
  newBlock = parentBlockParent.insertBefore(createNewBlock$1(), parentBlock);
25206
- const root = containerAndSiblingName(parentBlock, 'HR') || afterTable ? newBlock : prevBrOpt.getOr(parentBlock);
25280
+ const root = containerAndPreviousSiblingName(parentBlock, 'HR') || afterTable ? newBlock : prevBrOpt.getOr(parentBlock);
25207
25281
  moveToCaretPosition(editor, root);
25208
25282
  } else {
25209
25283
  const tmpRng = includeZwspInRange(rng).cloneRange();
@@ -25463,6 +25537,9 @@
25463
25537
  };
25464
25538
 
25465
25539
  const insertBreak = (breakType, editor, evt) => {
25540
+ if (editor.mode.isReadOnly()) {
25541
+ return;
25542
+ }
25466
25543
  if (!editor.selection.isCollapsed()) {
25467
25544
  execEditorDeleteCommand(editor);
25468
25545
  }
@@ -25478,6 +25555,9 @@
25478
25555
  }
25479
25556
  };
25480
25557
  const insert$1 = (editor, evt) => {
25558
+ if (editor.mode.isReadOnly()) {
25559
+ return;
25560
+ }
25481
25561
  const br = () => insertBreak(linebreak, editor, evt);
25482
25562
  const block = () => insertBreak(blockbreak, editor, evt);
25483
25563
  const logicalAction = getAction(editor, evt);
@@ -25840,16 +25920,17 @@
25840
25920
  });
25841
25921
  }
25842
25922
  nodeChanged(args = {}) {
25843
- const selection = this.editor.selection;
25923
+ const editor = this.editor;
25924
+ const selection = editor.selection;
25844
25925
  let node;
25845
- if (this.editor.initialized && selection && !shouldDisableNodeChange(this.editor) && !this.editor.mode.isReadOnly()) {
25846
- const root = this.editor.getBody();
25926
+ if (editor.initialized && selection && !shouldDisableNodeChange(editor)) {
25927
+ const root = editor.getBody();
25847
25928
  node = selection.getStart(true) || root;
25848
- if (node.ownerDocument !== this.editor.getDoc() || !this.editor.dom.isChildOf(node, root)) {
25929
+ if (node.ownerDocument !== editor.getDoc() || !editor.dom.isChildOf(node, root)) {
25849
25930
  node = root;
25850
25931
  }
25851
25932
  const parents = [];
25852
- this.editor.dom.getParent(node, node => {
25933
+ editor.dom.getParent(node, node => {
25853
25934
  if (node === root) {
25854
25935
  return true;
25855
25936
  } else {
@@ -25857,7 +25938,7 @@
25857
25938
  return false;
25858
25939
  }
25859
25940
  });
25860
- this.editor.dispatch('NodeChange', {
25941
+ editor.dispatch('NodeChange', {
25861
25942
  ...args,
25862
25943
  element: node,
25863
25944
  parents
@@ -28328,7 +28409,7 @@
28328
28409
  }), getTextPatternsLookup(editor));
28329
28410
  const hasDynamicPatterns = () => hasTextPatternsLookup(editor);
28330
28411
  editor.on('keydown', e => {
28331
- if (e.keyCode === 13 && !VK.modifierPressed(e) && editor.selection.isCollapsed()) {
28412
+ if (e.keyCode === 13 && !VK.modifierPressed(e) && editor.selection.isCollapsed() && editor.selection.isEditable()) {
28332
28413
  const patternSet = filterByTrigger(getPatternSet(), 'enter');
28333
28414
  const hasPatterns = patternSet.inlinePatterns.length > 0 || patternSet.blockPatterns.length > 0 || hasDynamicPatterns();
28334
28415
  if (hasPatterns && handleEnter(editor, patternSet)) {
@@ -28337,7 +28418,7 @@
28337
28418
  }
28338
28419
  }, true);
28339
28420
  editor.on('keydown', e => {
28340
- if (e.keyCode === 32 && editor.selection.isCollapsed()) {
28421
+ if (e.keyCode === 32 && editor.selection.isCollapsed() && editor.selection.isEditable()) {
28341
28422
  const patternSet = filterByTrigger(getPatternSet(), 'space');
28342
28423
  const hasPatterns = patternSet.blockPatterns.length > 0 || hasDynamicPatterns();
28343
28424
  if (hasPatterns && handleBlockPatternOnSpace(editor, patternSet)) {
@@ -28346,7 +28427,7 @@
28346
28427
  }
28347
28428
  }, true);
28348
28429
  const handleInlineTrigger = () => {
28349
- if (editor.selection.isCollapsed()) {
28430
+ if (editor.selection.isCollapsed() && editor.selection.isEditable()) {
28350
28431
  const patternSet = filterByTrigger(getPatternSet(), 'space');
28351
28432
  const hasPatterns = patternSet.inlinePatterns.length > 0 || hasDynamicPatterns();
28352
28433
  if (hasPatterns) {
@@ -28794,6 +28875,7 @@
28794
28875
  allow_svg_data_urls: getOption('allow_svg_data_urls'),
28795
28876
  allow_html_in_named_anchor: getOption('allow_html_in_named_anchor'),
28796
28877
  allow_script_urls: getOption('allow_script_urls'),
28878
+ allow_mathml_annotation_encodings: getOption('allow_mathml_annotation_encodings'),
28797
28879
  allow_unsafe_link_target: getOption('allow_unsafe_link_target'),
28798
28880
  convert_unsafe_embeds: getOption('convert_unsafe_embeds'),
28799
28881
  convert_fonts_to_spans: getOption('convert_fonts_to_spans'),
@@ -29076,7 +29158,7 @@
29076
29158
  body.disabled = true;
29077
29159
  editor.readonly = isReadOnly$1(editor);
29078
29160
  editor._editableRoot = hasEditableRoot$1(editor);
29079
- if (!editor.readonly && editor.hasEditableRoot()) {
29161
+ if (editor.hasEditableRoot()) {
29080
29162
  if (editor.inline && DOM$6.getStyle(body, 'position', true) === 'static') {
29081
29163
  body.style.position = 'relative';
29082
29164
  }
@@ -29335,7 +29417,8 @@
29335
29417
  hide: Optional.from(api.hide).getOr(noop),
29336
29418
  isEnabled: Optional.from(api.isEnabled).getOr(always),
29337
29419
  setEnabled: state => {
29338
- if (!editor.mode.isReadOnly()) {
29420
+ const shouldSkip = state && editor.mode.get() === 'readonly';
29421
+ if (!shouldSkip) {
29339
29422
  Optional.from(api.setEnabled).each(f => f(state));
29340
29423
  }
29341
29424
  }
@@ -29548,10 +29631,8 @@
29548
29631
  const setEditableRoot = (editor, state) => {
29549
29632
  if (editor._editableRoot !== state) {
29550
29633
  editor._editableRoot = state;
29551
- if (!editor.readonly) {
29552
- editor.getBody().contentEditable = String(editor.hasEditableRoot());
29553
- editor.nodeChanged();
29554
- }
29634
+ editor.getBody().contentEditable = String(editor.hasEditableRoot());
29635
+ editor.nodeChanged();
29555
29636
  fireEditableRootStateChange(editor, state);
29556
29637
  }
29557
29638
  };
@@ -29992,6 +30073,9 @@
29992
30073
 
29993
30074
  const registerCommands$4 = editor => {
29994
30075
  const applyLinkToSelection = (_command, _ui, value) => {
30076
+ if (editor.mode.isReadOnly()) {
30077
+ return;
30078
+ }
29995
30079
  const linkDetails = isString(value) ? { href: value } : value;
29996
30080
  const anchor = editor.dom.getParent(editor.selection.getNode(), 'a');
29997
30081
  if (isObject(linkDetails) && isString(linkDetails.href)) {
@@ -30029,6 +30113,9 @@
30029
30113
  return Optional.from(topParentBlock).map(SugarElement.fromDom);
30030
30114
  };
30031
30115
  const insert = (editor, before) => {
30116
+ if (editor.mode.isReadOnly()) {
30117
+ return;
30118
+ }
30032
30119
  const dom = editor.dom;
30033
30120
  const rng = editor.selection.getRng();
30034
30121
  const node = before ? editor.selection.getStart() : editor.selection.getEnd();
@@ -30233,7 +30320,6 @@
30233
30320
  }
30234
30321
  }
30235
30322
 
30236
- const internalContentEditableAttr = 'data-mce-contenteditable';
30237
30323
  const toggleClass = (elm, cls, state) => {
30238
30324
  if (has(elm, cls) && !state) {
30239
30325
  remove$6(elm, cls);
@@ -30250,18 +30336,6 @@
30250
30336
  const setContentEditable = (elm, state) => {
30251
30337
  elm.dom.contentEditable = state ? 'true' : 'false';
30252
30338
  };
30253
- const switchOffContentEditableTrue = elm => {
30254
- each$e(descendants(elm, '*[contenteditable="true"]'), elm => {
30255
- set$3(elm, internalContentEditableAttr, 'true');
30256
- setContentEditable(elm, false);
30257
- });
30258
- };
30259
- const switchOnContentEditableTrue = elm => {
30260
- each$e(descendants(elm, `*[${ internalContentEditableAttr }="true"]`), elm => {
30261
- remove$9(elm, internalContentEditableAttr);
30262
- setContentEditable(elm, true);
30263
- });
30264
- };
30265
30339
  const removeFakeSelection = editor => {
30266
30340
  Optional.from(editor.selection.getNode()).each(elm => {
30267
30341
  elm.removeAttribute('data-mce-selected');
@@ -30270,60 +30344,42 @@
30270
30344
  const restoreFakeSelection = editor => {
30271
30345
  editor.selection.setRng(editor.selection.getRng());
30272
30346
  };
30347
+ const setCommonEditorCommands = (editor, state) => {
30348
+ setEditorCommandState(editor, 'StyleWithCSS', state);
30349
+ setEditorCommandState(editor, 'enableInlineTableEditing', state);
30350
+ setEditorCommandState(editor, 'enableObjectResizing', state);
30351
+ };
30352
+ const setEditorReadonly = editor => {
30353
+ editor.readonly = true;
30354
+ editor.selection.controlSelection.hideResizeRect();
30355
+ editor._selectionOverrides.hideFakeCaret();
30356
+ removeFakeSelection(editor);
30357
+ };
30358
+ const unsetEditorReadonly = (editor, body) => {
30359
+ editor.readonly = false;
30360
+ if (editor.hasEditableRoot()) {
30361
+ setContentEditable(body, true);
30362
+ }
30363
+ setCommonEditorCommands(editor, false);
30364
+ if (hasEditorOrUiFocus(editor)) {
30365
+ editor.focus();
30366
+ }
30367
+ restoreFakeSelection(editor);
30368
+ editor.nodeChanged();
30369
+ };
30273
30370
  const toggleReadOnly = (editor, state) => {
30274
30371
  const body = SugarElement.fromDom(editor.getBody());
30275
30372
  toggleClass(body, 'mce-content-readonly', state);
30276
30373
  if (state) {
30277
- editor.selection.controlSelection.hideResizeRect();
30278
- editor._selectionOverrides.hideFakeCaret();
30279
- removeFakeSelection(editor);
30280
- editor.readonly = true;
30281
- setContentEditable(body, false);
30282
- switchOffContentEditableTrue(body);
30283
- } else {
30284
- editor.readonly = false;
30374
+ setEditorReadonly(editor);
30285
30375
  if (editor.hasEditableRoot()) {
30286
30376
  setContentEditable(body, true);
30287
30377
  }
30288
- switchOnContentEditableTrue(body);
30289
- setEditorCommandState(editor, 'StyleWithCSS', false);
30290
- setEditorCommandState(editor, 'enableInlineTableEditing', false);
30291
- setEditorCommandState(editor, 'enableObjectResizing', false);
30292
- if (hasEditorOrUiFocus(editor)) {
30293
- editor.focus();
30294
- }
30295
- restoreFakeSelection(editor);
30296
- editor.nodeChanged();
30297
- }
30298
- };
30299
- const isReadOnly = editor => editor.readonly;
30300
- const registerFilters = editor => {
30301
- editor.parser.addAttributeFilter('contenteditable', nodes => {
30302
- if (isReadOnly(editor)) {
30303
- each$e(nodes, node => {
30304
- node.attr(internalContentEditableAttr, node.attr('contenteditable'));
30305
- node.attr('contenteditable', 'false');
30306
- });
30307
- }
30308
- });
30309
- editor.serializer.addAttributeFilter(internalContentEditableAttr, nodes => {
30310
- if (isReadOnly(editor)) {
30311
- each$e(nodes, node => {
30312
- node.attr('contenteditable', node.attr(internalContentEditableAttr));
30313
- });
30314
- }
30315
- });
30316
- editor.serializer.addTempAttr(internalContentEditableAttr);
30317
- };
30318
- const registerReadOnlyContentFilters = editor => {
30319
- if (editor.serializer) {
30320
- registerFilters(editor);
30321
30378
  } else {
30322
- editor.on('PreInit', () => {
30323
- registerFilters(editor);
30324
- });
30379
+ unsetEditorReadonly(editor, body);
30325
30380
  }
30326
30381
  };
30382
+ const isReadOnly = editor => editor.readonly;
30327
30383
  const isClickEvent = e => e.type === 'click';
30328
30384
  const allowedEvents = ['copy'];
30329
30385
  const isReadOnlyAllowedEvent = e => contains$2(allowedEvents, e.type);
@@ -30350,16 +30406,32 @@
30350
30406
  }
30351
30407
  };
30352
30408
  const registerReadOnlySelectionBlockers = editor => {
30353
- editor.on('ShowCaret', e => {
30409
+ editor.on('beforeinput paste cut dragend dragover draggesture dragdrop drop drag', e => {
30354
30410
  if (isReadOnly(editor)) {
30355
30411
  e.preventDefault();
30356
30412
  }
30357
30413
  });
30358
- editor.on('ObjectSelected', e => {
30359
- if (isReadOnly(editor)) {
30414
+ editor.on('BeforeExecCommand', e => {
30415
+ if ((e.command === 'Undo' || e.command === 'Redo') && isReadOnly(editor)) {
30360
30416
  e.preventDefault();
30361
30417
  }
30362
30418
  });
30419
+ editor.on('input', e => {
30420
+ if (!e.isComposing && isReadOnly(editor)) {
30421
+ const undoLevel = editor.undoManager.add();
30422
+ if (isNonNullable(undoLevel)) {
30423
+ editor.undoManager.undo();
30424
+ }
30425
+ }
30426
+ });
30427
+ editor.on('compositionend', () => {
30428
+ if (isReadOnly(editor)) {
30429
+ const undoLevel = editor.undoManager.add();
30430
+ if (isNonNullable(undoLevel)) {
30431
+ editor.undoManager.undo();
30432
+ }
30433
+ }
30434
+ });
30363
30435
  };
30364
30436
 
30365
30437
  const nativeEvents = Tools.makeMap('focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange ' + 'mouseout mouseenter mouseleave wheel keydown keypress keyup input beforeinput contextmenu dragstart dragend dragover ' + 'draggesture dragdrop drop drag submit ' + 'compositionstart compositionend compositionupdate touchstart touchmove touchend touchcancel', ' ');
@@ -30553,7 +30625,7 @@
30553
30625
  }
30554
30626
  return editor.getBody();
30555
30627
  };
30556
- const isListening = editor => !editor.hidden && !isReadOnly(editor);
30628
+ const isListening = editor => !editor.hidden;
30557
30629
  const fireEvent = (editor, eventName, e) => {
30558
30630
  if (isListening(editor)) {
30559
30631
  editor.dispatch(eventName, e);
@@ -30872,7 +30944,6 @@
30872
30944
  editorReadOnly: true
30873
30945
  }
30874
30946
  });
30875
- registerReadOnlyContentFilters(editor);
30876
30947
  registerReadOnlySelectionBlockers(editor);
30877
30948
  return {
30878
30949
  isReadOnly: () => isReadOnly(editor),
@@ -31048,6 +31119,7 @@
31048
31119
  const icons = {};
31049
31120
  const contextMenus = {};
31050
31121
  const contextToolbars = {};
31122
+ const contexts = {};
31051
31123
  const sidebars = {};
31052
31124
  const views = {};
31053
31125
  const add = (collection, type) => (name, spec) => {
@@ -31057,6 +31129,7 @@
31057
31129
  };
31058
31130
  };
31059
31131
  const addIcon = (name, svgData) => icons[name.toLowerCase()] = svgData;
31132
+ const addContext = (name, pred) => contexts[name.toLowerCase()] = pred;
31060
31133
  return {
31061
31134
  addButton: add(buttons, 'button'),
31062
31135
  addGroupToolbarButton: add(buttons, 'grouptoolbarbutton'),
@@ -31073,6 +31146,7 @@
31073
31146
  addSidebar: add(sidebars, 'sidebar'),
31074
31147
  addView: add(views, 'views'),
31075
31148
  addIcon,
31149
+ addContext,
31076
31150
  getAll: () => ({
31077
31151
  buttons,
31078
31152
  menuItems,
@@ -31081,7 +31155,8 @@
31081
31155
  contextMenus,
31082
31156
  contextToolbars,
31083
31157
  sidebars,
31084
- views
31158
+ views,
31159
+ contexts
31085
31160
  })
31086
31161
  };
31087
31162
  };
@@ -31104,6 +31179,7 @@
31104
31179
  addGroupToolbarButton: bridge.addGroupToolbarButton,
31105
31180
  addToggleMenuItem: bridge.addToggleMenuItem,
31106
31181
  addView: bridge.addView,
31182
+ addContext: bridge.addContext,
31107
31183
  getAll: bridge.getAll
31108
31184
  };
31109
31185
  };
@@ -31540,8 +31616,8 @@
31540
31616
  documentBaseURL: null,
31541
31617
  suffix: null,
31542
31618
  majorVersion: '7',
31543
- minorVersion: '3.0',
31544
- releaseDate: '2024-08-07',
31619
+ minorVersion: '4.1',
31620
+ releaseDate: 'TBD',
31545
31621
  i18n: I18n,
31546
31622
  activeEditor: null,
31547
31623
  focusedEditor: null,