Repository: cure53/DOMPurify (language: javascript)

Issue:
Small syntax enhancement to TMPLIT_EXPR
> This issue proposes a small enhancement to the regexp in the `TMPLIT_EXPR` const.

### Background & Context

`const TMPLIT_EXPR = seal(/\${[\w\W]*}/gm);`

Projects that use XSLT can inline 3rd party libs before pushing out the response to the browser. While doing so they can perform code resolution/substitutions on ${} espressions. Unfortunately the `${[\w\W]*}` part can be misinterpreted as an expression even though it's in a regexp, and because it obviously doesn't resolve to anything during the transformation, it gets wiped out as a result and causes a syntax error when the browser gets it.
Result: `const TMPLIT_EXPR = seal(/\/gm);`

### Proposed workaround

The solution is extremely simple and doesn't change anything to how the regexp operates, and would improve compatibility with a wider range of mission critical projects build on "older" tech like XSLT.
All that is needed is to escape the first curly brace like so:
`const TMPLIT_EXPR = seal(/\$\{[\w\W]*}/gm);`

Would you like me to create a PR for this?



Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index acff2069..9fc4628c 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
 
 DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG.
 
-It's also very simple to use and get started with. DOMPurify was [started in February 2014](https://github.com/cure53/DOMPurify/commit/a630922616927373485e0e787ab19e73e3691b2b) and, meanwhile, has reached version **v3.2.1**.
+It's also very simple to use and get started with. DOMPurify was [started in February 2014](https://github.com/cure53/DOMPurify/commit/a630922616927373485e0e787ab19e73e3691b2b) and, meanwhile, has reached version **v3.2.2**.
 
 DOMPurify is written in JavaScript and works in all modern browsers (Safari (10+), Opera (15+), Edge, Firefox and Chrome - as well as almost anything else using Blink, Gecko or WebKit). It doesn't break on MSIE or other legacy browsers. It simply does nothing.
 
diff --git a/bower.json b/bower.json
index fc72979d..55a1e124 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
 {
   "name": "dompurify",
-  "version": "3.2.1",
+  "version": "3.2.2",
   "homepage": "https://github.com/cure53/DOMPurify",
   "author": "Cure53 <info@cure53.de>",
   "description": "A DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG",
diff --git a/dist/purify.cjs.d.ts b/dist/purify.cjs.d.ts
index 8659b7b8..7a812328 100644
--- a/dist/purify.cjs.d.ts
+++ b/dist/purify.cjs.d.ts
@@ -1,4 +1,5 @@
-/*! @license DOMPurify 3.2.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.1/LICENSE */
+/// <reference types="trusted-types" />
+/*! @license DOMPurify 3.2.2 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.2/LICENSE */
 
 /**
  * Configuration to control DOMPurify behavior.
@@ -297,7 +298,21 @@ interface DOMPurify {
      * @param entryPoint entry point for the hook to add
      * @param hookFunction function to execute
      */
-    addHook(entryPoint: BasicHookName, hookFunction: Hook): void;
+    addHook(entryPoint: BasicHookName, hookFunction: NodeHook): void;
+    /**
+     * Adds a DOMPurify hook.
+     *
+     * @param entryPoint entry point for the hook to add
+     * @param hookFunction function to execute
+     */
+    addHook(entryPoint: ElementHookName, hookFunction: ElementHook): void;
+    /**
+     * Adds a DOMPurify hook.
+     *
+     * @param entryPoint entry point for the hook to add
+     * @param hookFunction function to execute
+     */
+    addHook(entryPoint: DocumentFragmentHookName, hookFunction: DocumentFragmentHook): void;
     /**
      * Adds a DOMPurify hook.
      *
@@ -319,7 +334,23 @@ interface DOMPurify {
      * @param entryPoint entry point for the hook to remove
      * @returns removed(popped) hook
      */
-    removeHook(entryPoint: BasicHookName): Hook | undefined;
+    removeHook(entryPoint: BasicHookName): NodeHook | undefined;
+    /**
+     * Remove a DOMPurify hook at a given entryPoint
+     * (pops it from the stack of hooks if more are present)
+     *
+     * @param entryPoint entry point for the hook to remove
+     * @returns removed(popped) hook
+     */
+    removeHook(entryPoint: ElementHookName): ElementHook | undefined;
+    /**
+     * Remove a DOMPurify hook at a given entryPoint
+     * (pops it from the stack of hooks if more are present)
+     *
+     * @param entryPoint entry point for the hook to remove
+     * @returns removed(popped) hook
+     */
+    removeHook(entryPoint: DocumentFragmentHookName): DocumentFragmentHook | undefined;
     /**
      * Remove a DOMPurify hook at a given entryPoint
      * (pops it from the stack of hooks if more are present)
@@ -369,13 +400,17 @@ interface RemovedAttribute {
      */
     from: Node;
 }
-type BasicHookName = 'beforeSanitizeElements' | 'afterSanitizeElements' | 'beforeSanitizeAttributes' | 'afterSanitizeAttributes' | 'beforeSanitizeShadowDOM' | 'uponSanitizeShadowNode' | 'afterSanitizeShadowDOM';
+type BasicHookName = 'beforeSanitizeElements' | 'afterSanitizeElements' | 'uponSanitizeShadowNode';
+type ElementHookName = 'beforeSanitizeAttributes' | 'afterSanitizeAttributes';
+type DocumentFragmentHookName = 'beforeSanitizeShadowDOM' | 'afterSanitizeShadowDOM';
 type UponSanitizeElementHookName = 'uponSanitizeElement';
 type UponSanitizeAttributeHookName = 'uponSanitizeAttribute';
-type HookName = BasicHookName | UponSanitizeElementHookName | UponSanitizeAttributeHookName;
-type Hook = (this: DOMPurify, currentNode: Node, hookEvent: null, config: Config) => void;
+type HookName = BasicHookName | ElementHookName | DocumentFragmentHookName | UponSanitizeElementHookName | UponSanitizeAttributeHookName;
+type NodeHook = (this: DOMPurify, currentNode: Node, hookEvent: null, config: Config) => void;
+type ElementHook = (this: DOMPurify, currentNode: Element, hookEvent: null, config: Config) => void;
+type DocumentFragmentHook = (this: DOMPurify, currentNode: DocumentFragment, hookEvent: null, config: Config) => void;
 type UponSanitizeElementHook = (this: DOMPurify, currentNode: Node, hookEvent: UponSanitizeElementHookEvent, config: Config) => void;
-type UponSanitizeAttributeHook = (this: DOMPurify, currentNode: Node, hookEvent: UponSanitizeAttributeHookEvent, config: Config) => void;
+type UponSanitizeAttributeHook = (this: DOMPurify, currentNode: Element, hookEvent: UponSanitizeAttributeHookEvent, config: Config) => void;
 interface UponSanitizeElementHookEvent {
     tagName: string;
     allowedTags: Record<string, boolean>;
@@ -396,7 +431,7 @@ type WindowLike = Pick<typeof globalThis, 'DocumentFragment' | 'HTMLTemplateElem
     trustedTypes?: typeof window.trustedTypes;
 };
 
-export { type Config, type DOMPurify, type Hook, type HookName, type RemovedAttribute, type RemovedElement, type UponSanitizeAttributeHook, type UponSanitizeAttributeHookEvent, type UponSanitizeElementHook, type UponSanitizeElementHookEvent, type WindowLike };
+export { type Config, type DOMPurify, type DocumentFragmentHook, type ElementHook, type HookName, type NodeHook, type RemovedAttribute, type RemovedElement, type UponSanitizeAttributeHook, type UponSanitizeAttributeHookEvent, type UponSanitizeElementHook, type UponSanitizeElementHookEvent, type WindowLike };
 
 // @ts-ignore
 export = _default;
diff --git a/dist/purify.cjs.js b/dist/purify.cjs.js
index e27891b9..4d4cd69b 100644
--- a/dist/purify.cjs.js
+++ b/dist/purify.cjs.js
@@ -1,4 +1,4 @@
-/*! @license DOMPurify 3.2.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.1/LICENSE */
+/*! @license DOMPurify 3.2.2 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.2/LICENSE */
 
 'use strict';
 
@@ -281,10 +281,23 @@ const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedType
     return null;
   }
 };
+const _createHooksMap = function _createHooksMap() {
+  return {
+    afterSanitizeAttributes: [],
+    afterSanitizeElements: [],
+    afterSanitizeShadowDOM: [],
+    beforeSanitizeAttributes: [],
+    beforeSanitizeElements: [],
+    beforeSanitizeShadowDOM: [],
+    uponSanitizeAttribute: [],
+    uponSanitizeElement: [],
+    uponSanitizeShadowNode: []
+  };
+};
 function createDOMPurify() {
   let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
   const DOMPurify = root => createDOMPurify(root);
-  DOMPurify.version = '3.2.1';
+  DOMPurify.version = '3.2.2';
   DOMPurify.removed = [];
   if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document) {
     // Not running in a browser, provide a factory function
@@ -337,7 +350,7 @@ function createDOMPurify() {
   const {
     importNode
   } = originalDocument;
-  let hooks = {};
+  let hooks = _createHooksMap();
   /**
    * Expose whether this browser supports running the full DOMPurify.
    */
@@ -766,8 +779,8 @@ function createDOMPurify() {
       });
     }
     element.removeAttribute(name);
-    // We void attribute values for unremovable "is"" attributes
-    if (name === 'is' && !ALLOWED_ATTR[name]) {
+    // We void attribute values for unremovable "is" attributes
+    if (name === 'is') {
       if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
         try {
           _forceRemove(element);
@@ -858,11 +871,8 @@ function createDOMPurify() {
   const _isNode = function _isNode(value) {
     return typeof Node === 'function' && value instanceof Node;
   };
-  function _executeHook(entryPoint, currentNode, data) {
-    if (!hooks[entryPoint]) {
-      return;
-    }
-    arrayForEach(hooks[entryPoint], hook => {
+  function _executeHooks(hooks, currentNode, data) {
+    arrayForEach(hooks, hook => {
       hook.call(DOMPurify, currentNode, data, CONFIG);
     });
   }
@@ -878,7 +888,7 @@ function createDOMPurify() {
   const _sanitizeElements = function _sanitizeElements(currentNode) {
     let content = null;
     /* Execute a hook if present */
-    _executeHook('beforeSanitizeElements', currentNode, null);
+    _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
     /* Check if element is clobbered or can clobber */
     if (_isClobbered(currentNode)) {
       _forceRemove(currentNode);
@@ -887,7 +897,7 @@ function createDOMPurify() {
     /* Now let's check the element's type and name */
     const tagName = transformCaseFunc(currentNode.nodeName);
     /* Execute a hook if present */
-    _executeHook('uponSanitizeElement', currentNode, {
+    _executeHooks(hooks.uponSanitizeElement, currentNode, {
       tagName,
       allowedTags: ALLOWED_TAGS
     });
@@ -958,7 +968,7 @@ function createDOMPurify() {
       }
     }
     /* Execute a hook if present */
-    _executeHook('afterSanitizeElements', currentNode, null);
+    _executeHooks(hooks.afterSanitizeElements, currentNode, null);
     return false;
   };
   /**
@@ -1019,7 +1029,7 @@ function createDOMPurify() {
    */
   const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
     /* Execute a hook if present */
-    _executeHook('beforeSanitizeAttributes', currentNode, null);
+    _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
     const {
       attributes
     } = currentNode;
@@ -1050,7 +1060,7 @@ function createDOMPurify() {
       hookEvent.attrValue = value;
       hookEvent.keepAttr = true;
       hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
-      _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
+      _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
       value = hookEvent.attrValue;
       /* Full DOM Clobbering protection via namespace isolation,
        * Prefix id and name attributes with `user-content-`
@@ -1125,7 +1135,7 @@ function createDOMPurify() {
       } catch (_) {}
     }
     /* Execute a hook if present */
-    _executeHook('afterSanitizeAttributes', currentNode, null);
+    _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
   };
   /**
    * _sanitizeShadowDOM
@@ -1136,10 +1146,10 @@ function createDOMPurify() {
     let shadowNode = null;
     const shadowIterator = _createNodeIterator(fragment);
     /* Execute a hook if present */
-    _executeHook('beforeSanitizeShadowDOM', fragment, null);
+    _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
     while (shadowNode = shadowIterator.nextNode()) {
       /* Execute a hook if present */
-      _executeHook('uponSanitizeShadowNode', shadowNode, null);
+      _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
       /* Sanitize tags and elements */
       if (_sanitizeElements(shadowNode)) {
         continue;
@@ -1152,7 +1162,7 @@ function createDOMPurify() {
       _sanitizeAttributes(shadowNode);
     }
     /* Execute a hook if present */
-    _executeHook('afterSanitizeShadowDOM', fragment, null);
+    _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
   };
   // eslint-disable-next-line complexity
   DOMPurify.sanitize = function (dirty) {
@@ -1310,21 +1320,16 @@ function createDOMPurify() {
     if (typeof hookFunction !== 'function') {
       return;
     }
-    hooks[entryPoint] = hooks[entryPoint] || [];
     arrayPush(hooks[entryPoint], hookFunction);
   };
   DOMPurify.removeHook = function (entryPoint) {
-    if (hooks[entryPoint]) {
-      return arrayPop(hooks[entryPoint]);
-    }
+    return arrayPop(hooks[entryPoint]);
   };
   DOMPurify.removeHooks = function (entryPoint) {
-    if (hooks[entryPoint]) {
-      hooks[entryPoint] = [];
-    }
+    hooks[entryPoint] = [];
   };
   DOMPurify.removeAllHooks = function () {
-    hooks = {};
+    hooks = _createHooksMap();
   };
   return DOMPurify;
 }
diff --git a/dist/purify.cjs.js.map b/dist/purify.cjs.js.map
index 95abe2c9..0d89dcc6 100644
--- a/dist/purify.cjs.js.map
+++ b/dist/purify.cjs.js.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"purify.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
\ No newline at end of file
+{"version":3,"file":"purify.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
\ No newline at end of file
diff --git a/dist/purify.es.d.mts b/dist/purify.es.d.mts
index 365911c2..0c341aa5 100644
--- a/dist/purify.es.d.mts
+++ b/dist/purify.es.d.mts
@@ -1,4 +1,5 @@
-/*! @license DOMPurify 3.2.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.1/LICENSE */
+/// <reference types="trusted-types" />
+/*! @license DOMPurify 3.2.2 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.2/LICENSE */
 
 /**
  * Configuration to control DOMPurify behavior.
@@ -297,7 +298,21 @@ interface DOMPurify {
      * @param entryPoint entry point for the hook to add
      * @param hookFunction function to execute
      */
-    addHook(entryPoint: BasicHookName, hookFunction: Hook): void;
+    addHook(entryPoint: BasicHookName, hookFunction: NodeHook): void;
+    /**
+     * Adds a DOMPurify hook.
+     *
+     * @param entryPoint entry point for the hook to add
+     * @param hookFunction function to execute
+     */
+    addHook(entryPoint: ElementHookName, hookFunction: ElementHook): void;
+    /**
+     * Adds a DOMPurify hook.
+     *
+     * @param entryPoint entry point for the hook to add
+     * @param hookFunction function to execute
+     */
+    addHook(entryPoint: DocumentFragmentHookName, hookFunction: DocumentFragmentHook): void;
     /**
      * Adds a DOMPurify hook.
      *
@@ -319,7 +334,23 @@ interface DOMPurify {
      * @param entryPoint entry point for the hook to remove
      * @returns removed(popped) hook
      */
-    removeHook(entryPoint: BasicHookName): Hook | undefined;
+    removeHook(entryPoint: BasicHookName): NodeHook | undefined;
+    /**
+     * Remove a DOMPurify hook at a given entryPoint
+     * (pops it from the stack of hooks if more are present)
+     *
+     * @param entryPoint entry point for the hook to remove
+     * @returns removed(popped) hook
+     */
+    removeHook(entryPoint: ElementHookName): ElementHook | undefined;
+    /**
+     * Remove a DOMPurify hook at a given entryPoint
+     * (pops it from the stack of hooks if more are present)
+     *
+     * @param entryPoint entry point for the hook to remove
+     * @returns removed(popped) hook
+     */
+    removeHook(entryPoint: DocumentFragmentHookName): DocumentFragmentHook | undefined;
     /**
      * Remove a DOMPurify hook at a given entryPoint
      * (pops it from the stack of hooks if more are present)
@@ -369,13 +400,17 @@ interface RemovedAttribute {
      */
     from: Node;
 }
-type BasicHookName = 'beforeSanitizeElements' | 'afterSanitizeElements' | 'beforeSanitizeAttributes' | 'afterSanitizeAttributes' | 'beforeSanitizeShadowDOM' | 'uponSanitizeShadowNode' | 'afterSanitizeShadowDOM';
+type BasicHookName = 'beforeSanitizeElements' | 'afterSanitizeElements' | 'uponSanitizeShadowNode';
+type ElementHookName = 'beforeSanitizeAttributes' | 'afterSanitizeAttributes';
+type DocumentFragmentHookName = 'beforeSanitizeShadowDOM' | 'afterSanitizeShadowDOM';
 type UponSanitizeElementHookName = 'uponSanitizeElement';
 type UponSanitizeAttributeHookName = 'uponSanitizeAttribute';
-type HookName = BasicHookName | UponSanitizeElementHookName | UponSanitizeAttributeHookName;
-type Hook = (this: DOMPurify, currentNode: Node, hookEvent: null, config: Config) => void;
+type HookName = BasicHookName | ElementHookName | DocumentFragmentHookName | UponSanitizeElementHookName | UponSanitizeAttributeHookName;
+type NodeHook = (this: DOMPurify, currentNode: Node, hookEvent: null, config: Config) => void;
+type ElementHook = (this: DOMPurify, currentNode: Element, hookEvent: null, config: Config) => void;
+type DocumentFragmentHook = (this: DOMPurify, currentNode: DocumentFragment, hookEvent: null, config: Config) => void;
 type UponSanitizeElementHook = (this: DOMPurify, currentNode: Node, hookEvent: UponSanitizeElementHookEvent, config: Config) => void;
-type UponSanitizeAttributeHook = (this: DOMPurify, currentNode: Node, hookEvent: UponSanitizeAttributeHookEvent, config: Config) => void;
+type UponSanitizeAttributeHook = (this: DOMPurify, currentNode: Element, hookEvent: UponSanitizeAttributeHookEvent, config: Config) => void;
 interface UponSanitizeElementHookEvent {
     tagName: string;
     allowedTags: Record<string, boolean>;
@@ -396,4 +431,4 @@ type WindowLike = Pick<typeof globalThis, 'DocumentFragment' | 'HTMLTemplateElem
     trustedTypes?: typeof window.trustedTypes;
 };
 
-export { type Config, type DOMPurify, type Hook, type HookName, type RemovedAttribute, type RemovedElement, type UponSanitizeAttributeHook, type UponSanitizeAttributeHookEvent, type UponSanitizeElementHook, type UponSanitizeElementHookEvent, type WindowLike, _default as default };
+export { type Config, type DOMPurify, type DocumentFragmentHook, type ElementHook, type HookName, type NodeHook, type RemovedAttribute, type RemovedElement, type UponSanitizeAttributeHook, type UponSanitizeAttributeHookEvent, type UponSanitizeElementHook, type UponSanitizeElementHookEvent, type WindowLike, _default as default };
diff --git a/dist/purify.es.mjs b/dist/purify.es.mjs
index 9488ae97..18590ce5 100644
--- a/dist/purify.es.mjs
+++ b/dist/purify.es.mjs
@@ -1,4 +1,4 @@
-/*! @license DOMPurify 3.2.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.1/LICENSE */
+/*! @license DOMPurify 3.2.2 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.2/LICENSE */
 
 const {
   entries,
@@ -279,10 +279,23 @@ const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedType
     return null;
   }
 };
+const _createHooksMap = function _createHooksMap() {
+  return {
+    afterSanitizeAttributes: [],
+    afterSanitizeElements: [],
+    afterSanitizeShadowDOM: [],
+    beforeSanitizeAttributes: [],
+    beforeSanitizeElements: [],
+    beforeSanitizeShadowDOM: [],
+    uponSanitizeAttribute: [],
+    uponSanitizeElement: [],
+    uponSanitizeShadowNode: []
+  };
+};
 function createDOMPurify() {
   let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
   const DOMPurify = root => createDOMPurify(root);
-  DOMPurify.version = '3.2.1';
+  DOMPurify.version = '3.2.2';
   DOMPurify.removed = [];
   if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document) {
     // Not running in a browser, provide a factory function
@@ -335,7 +348,7 @@ function createDOMPurify() {
   const {
     importNode
   } = originalDocument;
-  let hooks = {};
+  let hooks = _createHooksMap();
   /**
    * Expose whether this browser supports running the full DOMPurify.
    */
@@ -764,8 +777,8 @@ function createDOMPurify() {
       });
     }
     element.removeAttribute(name);
-    // We void attribute values for unremovable "is"" attributes
-    if (name === 'is' && !ALLOWED_ATTR[name]) {
+    // We void attribute values for unremovable "is" attributes
+    if (name === 'is') {
       if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
         try {
           _forceRemove(element);
@@ -856,11 +869,8 @@ function createDOMPurify() {
   const _isNode = function _isNode(value) {
     return typeof Node === 'function' && value instanceof Node;
   };
-  function _executeHook(entryPoint, currentNode, data) {
-    if (!hooks[entryPoint]) {
-      return;
-    }
-    arrayForEach(hooks[entryPoint], hook => {
+  function _executeHooks(hooks, currentNode, data) {
+    arrayForEach(hooks, hook => {
       hook.call(DOMPurify, currentNode, data, CONFIG);
     });
   }
@@ -876,7 +886,7 @@ function createDOMPurify() {
   const _sanitizeElements = function _sanitizeElements(currentNode) {
     let content = null;
     /* Execute a hook if present */
-    _executeHook('beforeSanitizeElements', currentNode, null);
+    _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
     /* Check if element is clobbered or can clobber */
     if (_isClobbered(currentNode)) {
       _forceRemove(currentNode);
@@ -885,7 +895,7 @@ function createDOMPurify() {
     /* Now let's check the element's type and name */
     const tagName = transformCaseFunc(currentNode.nodeName);
     /* Execute a hook if present */
-    _executeHook('uponSanitizeElement', currentNode, {
+    _executeHooks(hooks.uponSanitizeElement, currentNode, {
       tagName,
       allowedTags: ALLOWED_TAGS
     });
@@ -956,7 +966,7 @@ function createDOMPurify() {
       }
     }
     /* Execute a hook if present */
-    _executeHook('afterSanitizeElements', currentNode, null);
+    _executeHooks(hooks.afterSanitizeElements, currentNode, null);
     return false;
   };
   /**
@@ -1017,7 +1027,7 @@ function createDOMPurify() {
    */
   const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
     /* Execute a hook if present */
-    _executeHook('beforeSanitizeAttributes', currentNode, null);
+    _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
     const {
       attributes
     } = currentNode;
@@ -1048,7 +1058,7 @@ function createDOMPurify() {
       hookEvent.attrValue = value;
       hookEvent.keepAttr = true;
       hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
-      _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
+      _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
       value = hookEvent.attrValue;
       /* Full DOM Clobbering protection via namespace isolation,
        * Prefix id and name attributes with `user-content-`
@@ -1123,7 +1133,7 @@ function createDOMPurify() {
       } catch (_) {}
     }
     /* Execute a hook if present */
-    _executeHook('afterSanitizeAttributes', currentNode, null);
+    _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
   };
   /**
    * _sanitizeShadowDOM
@@ -1134,10 +1144,10 @@ function createDOMPurify() {
     let shadowNode = null;
     const shadowIterator = _createNodeIterator(fragment);
     /* Execute a hook if present */
-    _executeHook('beforeSanitizeShadowDOM', fragment, null);
+    _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
     while (shadowNode = shadowIterator.nextNode()) {
       /* Execute a hook if present */
-      _executeHook('uponSanitizeShadowNode', shadowNode, null);
+      _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
       /* Sanitize tags and elements */
       if (_sanitizeElements(shadowNode)) {
         continue;
@@ -1150,7 +1160,7 @@ function createDOMPurify() {
       _sanitizeAttributes(shadowNode);
     }
     /* Execute a hook if present */
-    _executeHook('afterSanitizeShadowDOM', fragment, null);
+    _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
   };
   // eslint-disable-next-line complexity
   DOMPurify.sanitize = function (dirty) {
@@ -1308,21 +1318,16 @@ function createDOMPurify() {
     if (typeof hookFunction !== 'function') {
       return;
     }
-    hooks[entryPoint] = hooks[entryPoint] || [];
     arrayPush(hooks[entryPoint], hookFunction);
   };
   DOMPurify.removeHook = function (entryPoint) {
-    if (hooks[entryPoint]) {
-      return arrayPop(hooks[entryPoint]);
-    }
+    return arrayPop(hooks[entryPoint]);
   };
   DOMPurify.removeHooks = function (entryPoint) {
-    if (hooks[entryPoint]) {
-      hooks[entryPoint] = [];
-    }
+    hooks[entryPoint] = [];
   };
   DOMPurify.removeAllHooks = function () {
-    hooks = {};
+    hooks = _createHooksMap();
   };
   return DOMPurify;
 }
diff --git a/dist/purify.es.mjs.map b/dist/purify.es.mjs.map
index b60ac1e0..57c9f203 100644
--- a/dist/purify.es.mjs.map
+++ b/dist/purify.es.mjs.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"purify.es.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
\ No newline at end of file
+{"version":3,"file":"purify.es.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
\ No newline at end of file
diff --git a/dist/purify.js b/dist/purify.js
index 9087e82b..bd4cd4cc 100644
--- a/dist/purify.js
+++ b/dist/purify.js
@@ -1,4 +1,4 @@
-/*! @license DOMPurify 3.2.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.1/LICENSE */
+/*! @license DOMPurify 3.2.2 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.2/LICENSE */
 
 (function (global, factory) {
   typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
@@ -285,10 +285,23 @@
       return null;
     }
   };
+  const _createHooksMap = function _createHooksMap() {
+    return {
+      afterSanitizeAttributes: [],
+      afterSanitizeElements: [],
+      afterSanitizeShadowDOM: [],
+      beforeSanitizeAttributes: [],
+      beforeSanitizeElements: [],
+      beforeSanitizeShadowDOM: [],
+      uponSanitizeAttribute: [],
+      uponSanitizeElement: [],
+      uponSanitizeShadowNode: []
+    };
+  };
   function createDOMPurify() {
     let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
     const DOMPurify = root => createDOMPurify(root);
-    DOMPurify.version = '3.2.1';
+    DOMPurify.version = '3.2.2';
     DOMPurify.removed = [];
     if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document) {
       // Not running in a browser, provide a factory function
@@ -341,7 +354,7 @@
     const {
       importNode
     } = originalDocument;
-    let hooks = {};
+    let hooks = _createHooksMap();
     /**
      * Expose whether this browser supports running the full DOMPurify.
      */
@@ -770,8 +783,8 @@
         });
       }
       element.removeAttribute(name);
-      // We void attribute values for unremovable "is"" attributes
-      if (name === 'is' && !ALLOWED_ATTR[name]) {
+      // We void attribute values for unremovable "is" attributes
+      if (name === 'is') {
         if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
           try {
             _forceRemove(element);
@@ -862,11 +875,8 @@
     const _isNode = function _isNode(value) {
       return typeof Node === 'function' && value instanceof Node;
     };
-    function _executeHook(entryPoint, currentNode, data) {
-      if (!hooks[entryPoint]) {
-        return;
-      }
-      arrayForEach(hooks[entryPoint], hook => {
+    function _executeHooks(hooks, currentNode, data) {
+      arrayForEach(hooks, hook => {
         hook.call(DOMPurify, currentNode, data, CONFIG);
       });
     }
@@ -882,7 +892,7 @@
     const _sanitizeElements = function _sanitizeElements(currentNode) {
       let content = null;
       /* Execute a hook if present */
-      _executeHook('beforeSanitizeElements', currentNode, null);
+      _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
       /* Check if element is clobbered or can clobber */
       if (_isClobbered(currentNode)) {
         _forceRemove(currentNode);
@@ -891,7 +901,7 @@
       /* Now let's check the element's type and name */
       const tagName = transformCaseFunc(currentNode.nodeName);
       /* Execute a hook if present */
-      _executeHook('uponSanitizeElement', currentNode, {
+      _executeHooks(hooks.uponSanitizeElement, currentNode, {
         tagName,
         allowedTags: ALLOWED_TAGS
       });
@@ -962,7 +972,7 @@
         }
       }
       /* Execute a hook if present */
-      _executeHook('afterSanitizeElements', currentNode, null);
+      _executeHooks(hooks.afterSanitizeElements, currentNode, null);
       return false;
     };
     /**
@@ -1023,7 +1033,7 @@
      */
     const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
       /* Execute a hook if present */
-      _executeHook('beforeSanitizeAttributes', currentNode, null);
+      _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
       const {
         attributes
       } = currentNode;
@@ -1054,7 +1064,7 @@
         hookEvent.attrValue = value;
         hookEvent.keepAttr = true;
         hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
-        _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
+        _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
         value = hookEvent.attrValue;
         /* Full DOM Clobbering protection via namespace isolation,
          * Prefix id and name attributes with `user-content-`
@@ -1129,7 +1139,7 @@
         } catch (_) {}
       }
       /* Execute a hook if present */
-      _executeHook('afterSanitizeAttributes', currentNode, null);
+      _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
     };
     /**
      * _sanitizeShadowDOM
@@ -1140,10 +1150,10 @@
       let shadowNode = null;
       const shadowIterator = _createNodeIterator(fragment);
       /* Execute a hook if present */
-      _executeHook('beforeSanitizeShadowDOM', fragment, null);
+      _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
       while (shadowNode = shadowIterator.nextNode()) {
         /* Execute a hook if present */
-        _executeHook('uponSanitizeShadowNode', shadowNode, null);
+        _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
         /* Sanitize tags and elements */
         if (_sanitizeElements(shadowNode)) {
           continue;
@@ -1156,7 +1166,7 @@
         _sanitizeAttributes(shadowNode);
       }
       /* Execute a hook if present */
-      _executeHook('afterSanitizeShadowDOM', fragment, null);
+      _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
     };
     // eslint-disable-next-line complexity
     DOMPurify.sanitize = function (dirty) {
@@ -1314,21 +1324,16 @@
       if (typeof hookFunction !== 'function') {
         return;
       }
-      hooks[entryPoint] = hooks[entryPoint] || [];
       arrayPush(hooks[entryPoint], hookFunction);
     };
     DOMPurify.removeHook = function (entryPoint) {
-      if (hooks[entryPoint]) {
-        return arrayPop(hooks[entryPoint]);
-      }
+      return arrayPop(hooks[entryPoint]);
     };
     DOMPurify.removeHooks = function (entryPoint) {
-      if (hooks[entryPoint]) {
-        hooks[entryPoint] = [];
-      }
+      hooks[entryPoint] = [];
     };
     DOMPurify.removeAllHooks = function () {
-      hooks = {};
+      hooks = _createHooksMap();
     };
     return DOMPurify;
   }
diff --git a/dist/purify.js.map b/dist/purify.js.map
index e719347d..67b6f829 100644
--- a/dist/purify.js.map
+++ b/dist/purify.js.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"purify.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
\ No newline at end of file
+{"version":3,"file":"purify.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
\ No newline at end of file
diff --git a/dist/purify.min.js b/dist/purify.min.js
index fdf54c47..67443821 100644
--- a/dist/purify.min.js
+++ b/dist/purify.min.js
@@ -1,3 +1,3 @@
-/*! @license DOMPurify 3.2.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.1/LICENSE */
-!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).DOMPurify=t()}(this,(function(){"use strict";const{entries:e,setPrototypeOf:t,isFrozen:n,getPrototypeOf:o,getOwnPropertyDescriptor:r}=Object;let{freeze:i,seal:a,create:l}=Object,{apply:c,construct:s}="undefined"!=typeof Reflect&&Reflect;i||(i=function(e){return e}),a||(a=function(e){return e}),c||(c=function(e,t,n){return e.apply(t,n)}),s||(s=function(e,t){return new e(...t)});const u=b(Array.prototype.forEach),m=b(Array.prototype.pop),p=b(Array.prototype.push),f=b(String.prototype.toLowerCase),d=b(String.prototype.toString),h=b(String.prototype.match),g=b(String.prototype.replace),T=b(String.prototype.indexOf),y=b(String.prototype.trim),E=b(Object.prototype.hasOwnProperty),_=b(RegExp.prototype.test),A=(N=TypeError,function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return s(N,t)});var N;function b(e){return function(t){for(var n=arguments.length,o=new Array(n>1?n-1:0),r=1;r<n;r++)o[r-1]=arguments[r];return c(e,t,o)}}function S(e,o){let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:f;t&&t(e,null);let i=o.length;for(;i--;){let t=o[i];if("string"==typeof t){const e=r(t);e!==t&&(n(o)||(o[i]=e),t=e)}e[t]=!0}return e}function R(e){for(let t=0;t<e.length;t++){E(e,t)||(e[t]=null)}return e}function w(t){const n=l(null);for(const[o,r]of e(t)){E(t,o)&&(Array.isArray(r)?n[o]=R(r):r&&"object"==typeof r&&r.constructor===Object?n[o]=w(r):n[o]=r)}return n}function L(e,t){for(;null!==e;){const n=r(e,t);if(n){if(n.get)return b(n.get);if("function"==typeof n.value)return b(n.value)}e=o(e)}return function(){return null}}const v=i(["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"]),C=i(["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"]),O=i(["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"]),D=i(["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"]),x=i(["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"]),k=i(["maction","maligngroup","malignmark","mlongdiv","mscarries","mscarry","msgroup","mstack","msline","msrow","semantics","annotation","annotation-xml","mprescripts","none"]),I=i(["#text"]),M=i(["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"]),U=i(["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"]),P=i(["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"]),H=i(["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"]),F=a(/\{\{[\w\W]*|[\w\W]*\}\}/gm),z=a(/<%[\w\W]*|[\w\W]*%>/gm),B=a(/\${[\w\W]*}/gm),W=a(/^data-[\-\w.\u00B7-\uFFFF]/),G=a(/^aria-[\-\w]+$/),Y=a(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),j=a(/^(?:\w+script|data):/i),X=a(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),q=a(/^html$/i),K=a(/^[a-z][.\w]*(-[.\w]+)+$/i);var $=Object.freeze({__proto__:null,ARIA_ATTR:G,ATTR_WHITESPACE:X,CUSTOM_ELEMENT:K,DATA_ATTR:W,DOCTYPE_NAME:q,ERB_EXPR:z,IS_ALLOWED_URI:Y,IS_SCRIPT_OR_DATA:j,MUSTACHE_EXPR:F,TMPLIT_EXPR:B});const V=1,Z=3,J=7,Q=8,ee=9,te=function(){return"undefined"==typeof window?null:window};var ne=function t(){let n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:te();const o=e=>t(e);if(o.version="3.2.1",o.removed=[],!n||!n.document||n.document.nodeType!==ee)return o.isSupported=!1,o;let{document:r}=n;const a=r,c=a.currentScript,{DocumentFragment:s,HTMLTemplateElement:N,Node:b,Element:R,NodeFilter:F,NamedNodeMap:z=n.NamedNodeMap||n.MozNamedAttrMap,HTMLFormElement:B,DOMParser:W,trustedTypes:G}=n,j=R.prototype,X=L(j,"cloneNode"),K=L(j,"remove"),ne=L(j,"nextSibling"),oe=L(j,"childNodes"),re=L(j,"parentNode");if("function"==typeof N){const e=r.createElement("template");e.content&&e.content.ownerDocument&&(r=e.content.ownerDocument)}let ie,ae="";const{implementation:le,createNodeIterator:ce,createDocumentFragment:se,getElementsByTagName:ue}=r,{importNode:me}=a;let pe={};o.isSupported="function"==typeof e&&"function"==typeof re&&le&&void 0!==le.createHTMLDocument;const{MUSTACHE_EXPR:fe,ERB_EXPR:de,TMPLIT_EXPR:he,DATA_ATTR:ge,ARIA_ATTR:Te,IS_SCRIPT_OR_DATA:ye,ATTR_WHITESPACE:Ee,CUSTOM_ELEMENT:_e}=$;let{IS_ALLOWED_URI:Ae}=$,Ne=null;const be=S({},[...v,...C,...O,...x,...I]);let Se=null;const Re=S({},[...M,...U,...P,...H]);let we=Object.seal(l(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),Le=null,ve=null,Ce=!0,Oe=!0,De=!1,xe=!0,ke=!1,Ie=!0,Me=!1,Ue=!1,Pe=!1,He=!1,Fe=!1,ze=!1,Be=!0,We=!1,Ge=!0,Ye=!1,je={},Xe=null;const qe=S({},["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"]);let Ke=null;const $e=S({},["audio","video","img","source","image","track"]);let Ve=null;const Ze=S({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Je="http://www.w3.org/1998/Math/MathML",Qe="http://www.w3.org/2000/svg",et="http://www.w3.org/1999/xhtml";let tt=et,nt=!1,ot=null;const rt=S({},[Je,Qe,et],d);let it=S({},["mi","mo","mn","ms","mtext"]),at=S({},["annotation-xml"]);const lt=S({},["title","style","font","a","script"]);let ct=null;const st=["application/xhtml+xml","text/html"];let ut=null,mt=null;const pt=r.createElement("form"),ft=function(e){return e instanceof RegExp||e instanceof Function},dt=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!mt||mt!==e){if(e&&"object"==typeof e||(e={}),e=w(e),ct=-1===st.indexOf(e.PARSER_MEDIA_TYPE)?"text/html":e.PARSER_MEDIA_TYPE,ut="application/xhtml+xml"===ct?d:f,Ne=E(e,"ALLOWED_TAGS")?S({},e.ALLOWED_TAGS,ut):be,Se=E(e,"ALLOWED_ATTR")?S({},e.ALLOWED_ATTR,ut):Re,ot=E(e,"ALLOWED_NAMESPACES")?S({},e.ALLOWED_NAMESPACES,d):rt,Ve=E(e,"ADD_URI_SAFE_ATTR")?S(w(Ze),e.ADD_URI_SAFE_ATTR,ut):Ze,Ke=E(e,"ADD_DATA_URI_TAGS")?S(w($e),e.ADD_DATA_URI_TAGS,ut):$e,Xe=E(e,"FORBID_CONTENTS")?S({},e.FORBID_CONTENTS,ut):qe,Le=E(e,"FORBID_TAGS")?S({},e.FORBID_TAGS,ut):{},ve=E(e,"FORBID_ATTR")?S({},e.FORBID_ATTR,ut):{},je=!!E(e,"USE_PROFILES")&&e.USE_PROFILES,Ce=!1!==e.ALLOW_ARIA_ATTR,Oe=!1!==e.ALLOW_DATA_ATTR,De=e.ALLOW_UNKNOWN_PROTOCOLS||!1,xe=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,ke=e.SAFE_FOR_TEMPLATES||!1,Ie=!1!==e.SAFE_FOR_XML,Me=e.WHOLE_DOCUMENT||!1,He=e.RETURN_DOM||!1,Fe=e.RETURN_DOM_FRAGMENT||!1,ze=e.RETURN_TRUSTED_TYPE||!1,Pe=e.FORCE_BODY||!1,Be=!1!==e.SANITIZE_DOM,We=e.SANITIZE_NAMED_PROPS||!1,Ge=!1!==e.KEEP_CONTENT,Ye=e.IN_PLACE||!1,Ae=e.ALLOWED_URI_REGEXP||Y,tt=e.NAMESPACE||et,it=e.MATHML_TEXT_INTEGRATION_POINTS||it,at=e.HTML_INTEGRATION_POINTS||at,we=e.CUSTOM_ELEMENT_HANDLING||{},e.CUSTOM_ELEMENT_HANDLING&&ft(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(we.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&ft(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(we.attributeNameCheck=e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),e.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(we.allowCustomizedBuiltInElements=e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),ke&&(Oe=!1),Fe&&(He=!0),je&&(Ne=S({},I),Se=[],!0===je.html&&(S(Ne,v),S(Se,M)),!0===je.svg&&(S(Ne,C),S(Se,U),S(Se,H)),!0===je.svgFilters&&(S(Ne,O),S(Se,U),S(Se,H)),!0===je.mathMl&&(S(Ne,x),S(Se,P),S(Se,H))),e.ADD_TAGS&&(Ne===be&&(Ne=w(Ne)),S(Ne,e.ADD_TAGS,ut)),e.ADD_ATTR&&(Se===Re&&(Se=w(Se)),S(Se,e.ADD_ATTR,ut)),e.ADD_URI_SAFE_ATTR&&S(Ve,e.ADD_URI_SAFE_ATTR,ut),e.FORBID_CONTENTS&&(Xe===qe&&(Xe=w(Xe)),S(Xe,e.FORBID_CONTENTS,ut)),Ge&&(Ne["#text"]=!0),Me&&S(Ne,["html","head","body"]),Ne.table&&(S(Ne,["tbody"]),delete Le.tbody),e.TRUSTED_TYPES_POLICY){if("function"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw A('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw A('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');ie=e.TRUSTED_TYPES_POLICY,ae=ie.createHTML("")}else void 0===ie&&(ie=function(e,t){if("object"!=typeof e||"function"!=typeof e.createPolicy)return null;let n=null;const o="data-tt-policy-suffix";t&&t.hasAttribute(o)&&(n=t.getAttribute(o));const r="dompurify"+(n?"#"+n:"");try{return e.createPolicy(r,{createHTML:e=>e,createScriptURL:e=>e})}catch(e){return console.warn("TrustedTypes policy "+r+" could not be created."),null}}(G,c)),null!==ie&&"string"==typeof ae&&(ae=ie.createHTML(""));i&&i(e),mt=e}},ht=S({},[...C,...O,...D]),gt=S({},[...x,...k]),Tt=function(e){p(o.removed,{element:e});try{re(e).removeChild(e)}catch(t){K(e)}},yt=function(e,t){try{p(o.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){p(o.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!Se[e])if(He||Fe)try{Tt(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},Et=function(e){let t=null,n=null;if(Pe)e="<remove></remove>"+e;else{const t=h(e,/^[\r\n\t ]+/);n=t&&t[0]}"application/xhtml+xml"===ct&&tt===et&&(e='<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>'+e+"</body></html>");const o=ie?ie.createHTML(e):e;if(tt===et)try{t=(new W).parseFromString(o,ct)}catch(e){}if(!t||!t.documentElement){t=le.createDocument(tt,"template",null);try{t.documentElement.innerHTML=nt?ae:o}catch(e){}}const i=t.body||t.documentElement;return e&&n&&i.insertBefore(r.createTextNode(n),i.childNodes[0]||null),tt===et?ue.call(t,Me?"html":"body")[0]:Me?t.documentElement:i},_t=function(e){return ce.call(e.ownerDocument||e,e,F.SHOW_ELEMENT|F.SHOW_COMMENT|F.SHOW_TEXT|F.SHOW_PROCESSING_INSTRUCTION|F.SHOW_CDATA_SECTION,null)},At=function(e){return e instanceof B&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof z)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},Nt=function(e){return"function"==typeof b&&e instanceof b};function bt(e,t,n){pe[e]&&u(pe[e],(e=>{e.call(o,t,n,mt)}))}const St=function(e){let t=null;if(bt("beforeSanitizeElements",e,null),At(e))return Tt(e),!0;const n=ut(e.nodeName);if(bt("uponSanitizeElement",e,{tagName:n,allowedTags:Ne}),e.hasChildNodes()&&!Nt(e.firstElementChild)&&_(/<[/\w]/g,e.innerHTML)&&_(/<[/\w]/g,e.textContent))return Tt(e),!0;if(e.nodeType===J)return Tt(e),!0;if(Ie&&e.nodeType===Q&&_(/<[/\w]/g,e.data))return Tt(e),!0;if(!Ne[n]||Le[n]){if(!Le[n]&&wt(n)){if(we.tagNameCheck instanceof RegExp&&_(we.tagNameCheck,n))return!1;if(we.tagNameCheck instanceof Function&&we.tagNameCheck(n))return!1}if(Ge&&!Xe[n]){const t=re(e)||e.parentNode,n=oe(e)||e.childNodes;if(n&&t){for(let o=n.length-1;o>=0;--o){const r=X(n[o],!0);r.__removalCount=(e.__removalCount||0)+1,t.insertBefore(r,ne(e))}}}return Tt(e),!0}return e instanceof R&&!function(e){let t=re(e);t&&t.tagName||(t={namespaceURI:tt,tagName:"template"});const n=f(e.tagName),o=f(t.tagName);return!!ot[e.namespaceURI]&&(e.namespaceURI===Qe?t.namespaceURI===et?"svg"===n:t.namespaceURI===Je?"svg"===n&&("annotation-xml"===o||it[o]):Boolean(ht[n]):e.namespaceURI===Je?t.namespaceURI===et?"math"===n:t.namespaceURI===Qe?"math"===n&&at[o]:Boolean(gt[n]):e.namespaceURI===et?!(t.namespaceURI===Qe&&!at[o])&&!(t.namespaceURI===Je&&!it[o])&&!gt[n]&&(lt[n]||!ht[n]):!("application/xhtml+xml"!==ct||!ot[e.namespaceURI]))}(e)?(Tt(e),!0):"noscript"!==n&&"noembed"!==n&&"noframes"!==n||!_(/<\/no(script|embed|frames)/i,e.innerHTML)?(ke&&e.nodeType===Z&&(t=e.textContent,u([fe,de,he],(e=>{t=g(t,e," ")})),e.textContent!==t&&(p(o.removed,{element:e.cloneNode()}),e.textContent=t)),bt("afterSanitizeElements",e,null),!1):(Tt(e),!0)},Rt=function(e,t,n){if(Be&&("id"===t||"name"===t)&&(n in r||n in pt))return!1;if(Oe&&!ve[t]&&_(ge,t));else if(Ce&&_(Te,t));else if(!Se[t]||ve[t]){if(!(wt(e)&&(we.tagNameCheck instanceof RegExp&&_(we.tagNameCheck,e)||we.tagNameCheck instanceof Function&&we.tagNameCheck(e))&&(we.attributeNameCheck instanceof RegExp&&_(we.attributeNameCheck,t)||we.attributeNameCheck instanceof Function&&we.attributeNameCheck(t))||"is"===t&&we.allowCustomizedBuiltInElements&&(we.tagNameCheck instanceof RegExp&&_(we.tagNameCheck,n)||we.tagNameCheck instanceof Function&&we.tagNameCheck(n))))return!1}else if(Ve[t]);else if(_(Ae,g(n,Ee,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==T(n,"data:")||!Ke[e]){if(De&&!_(ye,g(n,Ee,"")));else if(n)return!1}else;return!0},wt=function(e){return"annotation-xml"!==e&&h(e,_e)},Lt=function(e){bt("beforeSanitizeAttributes",e,null);const{attributes:t}=e;if(!t)return;const n={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:Se,forceKeepAttr:void 0};let r=t.length;for(;r--;){const i=t[r],{name:a,namespaceURI:l,value:c}=i,s=ut(a);let p="value"===a?c:y(c);if(n.attrName=s,n.attrValue=p,n.keepAttr=!0,n.forceKeepAttr=void 0,bt("uponSanitizeAttribute",e,n),p=n.attrValue,!We||"id"!==s&&"name"!==s||(yt(a,e),p="user-content-"+p),Ie&&_(/((--!?|])>)|<\/(style|title)/i,p)){yt(a,e);continue}if(n.forceKeepAttr)continue;if(yt(a,e),!n.keepAttr)continue;if(!xe&&_(/\/>/i,p)){yt(a,e);continue}ke&&u([fe,de,he],(e=>{p=g(p,e," ")}));const f=ut(e.nodeName);if(Rt(f,s,p)){if(ie&&"object"==typeof G&&"function"==typeof G.getAttributeType)if(l);else switch(G.getAttributeType(f,s)){case"TrustedHTML":p=ie.createHTML(p);break;case"TrustedScriptURL":p=ie.createScriptURL(p)}try{l?e.setAttributeNS(l,a,p):e.setAttribute(a,p),At(e)?Tt(e):m(o.removed)}catch(e){}}}bt("afterSanitizeAttributes",e,null)},vt=function e(t){let n=null;const o=_t(t);for(bt("beforeSanitizeShadowDOM",t,null);n=o.nextNode();)bt("uponSanitizeShadowNode",n,null),St(n)||(n.content instanceof s&&e(n.content),Lt(n));bt("afterSanitizeShadowDOM",t,null)};return o.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=null,r=null,i=null,l=null;if(nt=!e,nt&&(e="\x3c!--\x3e"),"string"!=typeof e&&!Nt(e)){if("function"!=typeof e.toString)throw A("toString is not a function");if("string"!=typeof(e=e.toString()))throw A("dirty is not a string, aborting")}if(!o.isSupported)return e;if(Ue||dt(t),o.removed=[],"string"==typeof e&&(Ye=!1),Ye){if(e.nodeName){const t=ut(e.nodeName);if(!Ne[t]||Le[t])throw A("root node is forbidden and cannot be sanitized in-place")}}else if(e instanceof b)n=Et("\x3c!----\x3e"),r=n.ownerDocument.importNode(e,!0),r.nodeType===V&&"BODY"===r.nodeName||"HTML"===r.nodeName?n=r:n.appendChild(r);else{if(!He&&!ke&&!Me&&-1===e.indexOf("<"))return ie&&ze?ie.createHTML(e):e;if(n=Et(e),!n)return He?null:ze?ae:""}n&&Pe&&Tt(n.firstChild);const c=_t(Ye?e:n);for(;i=c.nextNode();)St(i)||(i.content instanceof s&&vt(i.content),Lt(i));if(Ye)return e;if(He){if(Fe)for(l=se.call(n.ownerDocument);n.firstChild;)l.appendChild(n.firstChild);else l=n;return(Se.shadowroot||Se.shadowrootmode)&&(l=me.call(a,l,!0)),l}let m=Me?n.outerHTML:n.innerHTML;return Me&&Ne["!doctype"]&&n.ownerDocument&&n.ownerDocument.doctype&&n.ownerDocument.doctype.name&&_(q,n.ownerDocument.doctype.name)&&(m="<!DOCTYPE "+n.ownerDocument.doctype.name+">\n"+m),ke&&u([fe,de,he],(e=>{m=g(m,e," ")})),ie&&ze?ie.createHTML(m):m},o.setConfig=function(){dt(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),Ue=!0},o.clearConfig=function(){mt=null,Ue=!1},o.isValidAttribute=function(e,t,n){mt||dt({});const o=ut(e),r=ut(t);return Rt(o,r,n)},o.addHook=function(e,t){"function"==typeof t&&(pe[e]=pe[e]||[],p(pe[e],t))},o.removeHook=function(e){if(pe[e])return m(pe[e])},o.removeHooks=function(e){pe[e]&&(pe[e]=[])},o.removeAllHooks=function(){pe={}},o}();return ne}));
+/*! @license DOMPurify 3.2.2 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.2/LICENSE */
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).DOMPurify=t()}(this,(function(){"use strict";const{entries:e,setPrototypeOf:t,isFrozen:n,getPrototypeOf:o,getOwnPropertyDescriptor:r}=Object;let{freeze:i,seal:a,create:l}=Object,{apply:c,construct:s}="undefined"!=typeof Reflect&&Reflect;i||(i=function(e){return e}),a||(a=function(e){return e}),c||(c=function(e,t,n){return e.apply(t,n)}),s||(s=function(e,t){return new e(...t)});const u=b(Array.prototype.forEach),m=b(Array.prototype.pop),p=b(Array.prototype.push),f=b(String.prototype.toLowerCase),d=b(String.prototype.toString),h=b(String.prototype.match),g=b(String.prototype.replace),T=b(String.prototype.indexOf),y=b(String.prototype.trim),E=b(Object.prototype.hasOwnProperty),A=b(RegExp.prototype.test),_=(S=TypeError,function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return s(S,t)});var S;function b(e){return function(t){for(var n=arguments.length,o=new Array(n>1?n-1:0),r=1;r<n;r++)o[r-1]=arguments[r];return c(e,t,o)}}function N(e,o){let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:f;t&&t(e,null);let i=o.length;for(;i--;){let t=o[i];if("string"==typeof t){const e=r(t);e!==t&&(n(o)||(o[i]=e),t=e)}e[t]=!0}return e}function R(e){for(let t=0;t<e.length;t++){E(e,t)||(e[t]=null)}return e}function w(t){const n=l(null);for(const[o,r]of e(t)){E(t,o)&&(Array.isArray(r)?n[o]=R(r):r&&"object"==typeof r&&r.constructor===Object?n[o]=w(r):n[o]=r)}return n}function O(e,t){for(;null!==e;){const n=r(e,t);if(n){if(n.get)return b(n.get);if("function"==typeof n.value)return b(n.value)}e=o(e)}return function(){return null}}const D=i(["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"]),L=i(["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"]),v=i(["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"]),C=i(["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"]),x=i(["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"]),M=i(["maction","maligngroup","malignmark","mlongdiv","mscarries","mscarry","msgroup","mstack","msline","msrow","semantics","annotation","annotation-xml","mprescripts","none"]),k=i(["#text"]),I=i(["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"]),U=i(["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"]),z=i(["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"]),P=i(["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"]),H=a(/\{\{[\w\W]*|[\w\W]*\}\}/gm),F=a(/<%[\w\W]*|[\w\W]*%>/gm),B=a(/\${[\w\W]*}/gm),W=a(/^data-[\-\w.\u00B7-\uFFFF]/),G=a(/^aria-[\-\w]+$/),Y=a(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),j=a(/^(?:\w+script|data):/i),X=a(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),q=a(/^html$/i),K=a(/^[a-z][.\w]*(-[.\w]+)+$/i);var $=Object.freeze({__proto__:null,ARIA_ATTR:G,ATTR_WHITESPACE:X,CUSTOM_ELEMENT:K,DATA_ATTR:W,DOCTYPE_NAME:q,ERB_EXPR:F,IS_ALLOWED_URI:Y,IS_SCRIPT_OR_DATA:j,MUSTACHE_EXPR:H,TMPLIT_EXPR:B});const V=1,Z=3,J=7,Q=8,ee=9,te=function(){return"undefined"==typeof window?null:window};var ne=function t(){let n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:te();const o=e=>t(e);if(o.version="3.2.2",o.removed=[],!n||!n.document||n.document.nodeType!==ee)return o.isSupported=!1,o;let{document:r}=n;const a=r,c=a.currentScript,{DocumentFragment:s,HTMLTemplateElement:S,Node:b,Element:R,NodeFilter:H,NamedNodeMap:F=n.NamedNodeMap||n.MozNamedAttrMap,HTMLFormElement:B,DOMParser:W,trustedTypes:G}=n,j=R.prototype,X=O(j,"cloneNode"),K=O(j,"remove"),ne=O(j,"nextSibling"),oe=O(j,"childNodes"),re=O(j,"parentNode");if("function"==typeof S){const e=r.createElement("template");e.content&&e.content.ownerDocument&&(r=e.content.ownerDocument)}let ie,ae="";const{implementation:le,createNodeIterator:ce,createDocumentFragment:se,getElementsByTagName:ue}=r,{importNode:me}=a;let pe={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]};o.isSupported="function"==typeof e&&"function"==typeof re&&le&&void 0!==le.createHTMLDocument;const{MUSTACHE_EXPR:fe,ERB_EXPR:de,TMPLIT_EXPR:he,DATA_ATTR:ge,ARIA_ATTR:Te,IS_SCRIPT_OR_DATA:ye,ATTR_WHITESPACE:Ee,CUSTOM_ELEMENT:Ae}=$;let{IS_ALLOWED_URI:_e}=$,Se=null;const be=N({},[...D,...L,...v,...x,...k]);let Ne=null;const Re=N({},[...I,...U,...z,...P]);let we=Object.seal(l(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),Oe=null,De=null,Le=!0,ve=!0,Ce=!1,xe=!0,Me=!1,ke=!0,Ie=!1,Ue=!1,ze=!1,Pe=!1,He=!1,Fe=!1,Be=!0,We=!1,Ge=!0,Ye=!1,je={},Xe=null;const qe=N({},["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"]);let Ke=null;const $e=N({},["audio","video","img","source","image","track"]);let Ve=null;const Ze=N({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Je="http://www.w3.org/1998/Math/MathML",Qe="http://www.w3.org/2000/svg",et="http://www.w3.org/1999/xhtml";let tt=et,nt=!1,ot=null;const rt=N({},[Je,Qe,et],d);let it=N({},["mi","mo","mn","ms","mtext"]),at=N({},["annotation-xml"]);const lt=N({},["title","style","font","a","script"]);let ct=null;const st=["application/xhtml+xml","text/html"];let ut=null,mt=null;const pt=r.createElement("form"),ft=function(e){return e instanceof RegExp||e instanceof Function},dt=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!mt||mt!==e){if(e&&"object"==typeof e||(e={}),e=w(e),ct=-1===st.indexOf(e.PARSER_MEDIA_TYPE)?"text/html":e.PARSER_MEDIA_TYPE,ut="application/xhtml+xml"===ct?d:f,Se=E(e,"ALLOWED_TAGS")?N({},e.ALLOWED_TAGS,ut):be,Ne=E(e,"ALLOWED_ATTR")?N({},e.ALLOWED_ATTR,ut):Re,ot=E(e,"ALLOWED_NAMESPACES")?N({},e.ALLOWED_NAMESPACES,d):rt,Ve=E(e,"ADD_URI_SAFE_ATTR")?N(w(Ze),e.ADD_URI_SAFE_ATTR,ut):Ze,Ke=E(e,"ADD_DATA_URI_TAGS")?N(w($e),e.ADD_DATA_URI_TAGS,ut):$e,Xe=E(e,"FORBID_CONTENTS")?N({},e.FORBID_CONTENTS,ut):qe,Oe=E(e,"FORBID_TAGS")?N({},e.FORBID_TAGS,ut):{},De=E(e,"FORBID_ATTR")?N({},e.FORBID_ATTR,ut):{},je=!!E(e,"USE_PROFILES")&&e.USE_PROFILES,Le=!1!==e.ALLOW_ARIA_ATTR,ve=!1!==e.ALLOW_DATA_ATTR,Ce=e.ALLOW_UNKNOWN_PROTOCOLS||!1,xe=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,Me=e.SAFE_FOR_TEMPLATES||!1,ke=!1!==e.SAFE_FOR_XML,Ie=e.WHOLE_DOCUMENT||!1,Pe=e.RETURN_DOM||!1,He=e.RETURN_DOM_FRAGMENT||!1,Fe=e.RETURN_TRUSTED_TYPE||!1,ze=e.FORCE_BODY||!1,Be=!1!==e.SANITIZE_DOM,We=e.SANITIZE_NAMED_PROPS||!1,Ge=!1!==e.KEEP_CONTENT,Ye=e.IN_PLACE||!1,_e=e.ALLOWED_URI_REGEXP||Y,tt=e.NAMESPACE||et,it=e.MATHML_TEXT_INTEGRATION_POINTS||it,at=e.HTML_INTEGRATION_POINTS||at,we=e.CUSTOM_ELEMENT_HANDLING||{},e.CUSTOM_ELEMENT_HANDLING&&ft(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(we.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&ft(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(we.attributeNameCheck=e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),e.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(we.allowCustomizedBuiltInElements=e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Me&&(ve=!1),He&&(Pe=!0),je&&(Se=N({},k),Ne=[],!0===je.html&&(N(Se,D),N(Ne,I)),!0===je.svg&&(N(Se,L),N(Ne,U),N(Ne,P)),!0===je.svgFilters&&(N(Se,v),N(Ne,U),N(Ne,P)),!0===je.mathMl&&(N(Se,x),N(Ne,z),N(Ne,P))),e.ADD_TAGS&&(Se===be&&(Se=w(Se)),N(Se,e.ADD_TAGS,ut)),e.ADD_ATTR&&(Ne===Re&&(Ne=w(Ne)),N(Ne,e.ADD_ATTR,ut)),e.ADD_URI_SAFE_ATTR&&N(Ve,e.ADD_URI_SAFE_ATTR,ut),e.FORBID_CONTENTS&&(Xe===qe&&(Xe=w(Xe)),N(Xe,e.FORBID_CONTENTS,ut)),Ge&&(Se["#text"]=!0),Ie&&N(Se,["html","head","body"]),Se.table&&(N(Se,["tbody"]),delete Oe.tbody),e.TRUSTED_TYPES_POLICY){if("function"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw _('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw _('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');ie=e.TRUSTED_TYPES_POLICY,ae=ie.createHTML("")}else void 0===ie&&(ie=function(e,t){if("object"!=typeof e||"function"!=typeof e.createPolicy)return null;let n=null;const o="data-tt-policy-suffix";t&&t.hasAttribute(o)&&(n=t.getAttribute(o));const r="dompurify"+(n?"#"+n:"");try{return e.createPolicy(r,{createHTML:e=>e,createScriptURL:e=>e})}catch(e){return console.warn("TrustedTypes policy "+r+" could not be created."),null}}(G,c)),null!==ie&&"string"==typeof ae&&(ae=ie.createHTML(""));i&&i(e),mt=e}},ht=N({},[...L,...v,...C]),gt=N({},[...x,...M]),Tt=function(e){p(o.removed,{element:e});try{re(e).removeChild(e)}catch(t){K(e)}},yt=function(e,t){try{p(o.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){p(o.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e)if(Pe||He)try{Tt(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},Et=function(e){let t=null,n=null;if(ze)e="<remove></remove>"+e;else{const t=h(e,/^[\r\n\t ]+/);n=t&&t[0]}"application/xhtml+xml"===ct&&tt===et&&(e='<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>'+e+"</body></html>");const o=ie?ie.createHTML(e):e;if(tt===et)try{t=(new W).parseFromString(o,ct)}catch(e){}if(!t||!t.documentElement){t=le.createDocument(tt,"template",null);try{t.documentElement.innerHTML=nt?ae:o}catch(e){}}const i=t.body||t.documentElement;return e&&n&&i.insertBefore(r.createTextNode(n),i.childNodes[0]||null),tt===et?ue.call(t,Ie?"html":"body")[0]:Ie?t.documentElement:i},At=function(e){return ce.call(e.ownerDocument||e,e,H.SHOW_ELEMENT|H.SHOW_COMMENT|H.SHOW_TEXT|H.SHOW_PROCESSING_INSTRUCTION|H.SHOW_CDATA_SECTION,null)},_t=function(e){return e instanceof B&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof F)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},St=function(e){return"function"==typeof b&&e instanceof b};function bt(e,t,n){u(e,(e=>{e.call(o,t,n,mt)}))}const Nt=function(e){let t=null;if(bt(pe.beforeSanitizeElements,e,null),_t(e))return Tt(e),!0;const n=ut(e.nodeName);if(bt(pe.uponSanitizeElement,e,{tagName:n,allowedTags:Se}),e.hasChildNodes()&&!St(e.firstElementChild)&&A(/<[/\w]/g,e.innerHTML)&&A(/<[/\w]/g,e.textContent))return Tt(e),!0;if(e.nodeType===J)return Tt(e),!0;if(ke&&e.nodeType===Q&&A(/<[/\w]/g,e.data))return Tt(e),!0;if(!Se[n]||Oe[n]){if(!Oe[n]&&wt(n)){if(we.tagNameCheck instanceof RegExp&&A(we.tagNameCheck,n))return!1;if(we.tagNameCheck instanceof Function&&we.tagNameCheck(n))return!1}if(Ge&&!Xe[n]){const t=re(e)||e.parentNode,n=oe(e)||e.childNodes;if(n&&t){for(let o=n.length-1;o>=0;--o){const r=X(n[o],!0);r.__removalCount=(e.__removalCount||0)+1,t.insertBefore(r,ne(e))}}}return Tt(e),!0}return e instanceof R&&!function(e){let t=re(e);t&&t.tagName||(t={namespaceURI:tt,tagName:"template"});const n=f(e.tagName),o=f(t.tagName);return!!ot[e.namespaceURI]&&(e.namespaceURI===Qe?t.namespaceURI===et?"svg"===n:t.namespaceURI===Je?"svg"===n&&("annotation-xml"===o||it[o]):Boolean(ht[n]):e.namespaceURI===Je?t.namespaceURI===et?"math"===n:t.namespaceURI===Qe?"math"===n&&at[o]:Boolean(gt[n]):e.namespaceURI===et?!(t.namespaceURI===Qe&&!at[o])&&!(t.namespaceURI===Je&&!it[o])&&!gt[n]&&(lt[n]||!ht[n]):!("application/xhtml+xml"!==ct||!ot[e.namespaceURI]))}(e)?(Tt(e),!0):"noscript"!==n&&"noembed"!==n&&"noframes"!==n||!A(/<\/no(script|embed|frames)/i,e.innerHTML)?(Me&&e.nodeType===Z&&(t=e.textContent,u([fe,de,he],(e=>{t=g(t,e," ")})),e.textContent!==t&&(p(o.removed,{element:e.cloneNode()}),e.textContent=t)),bt(pe.afterSanitizeElements,e,null),!1):(Tt(e),!0)},Rt=function(e,t,n){if(Be&&("id"===t||"name"===t)&&(n in r||n in pt))return!1;if(ve&&!De[t]&&A(ge,t));else if(Le&&A(Te,t));else if(!Ne[t]||De[t]){if(!(wt(e)&&(we.tagNameCheck instanceof RegExp&&A(we.tagNameCheck,e)||we.tagNameCheck instanceof Function&&we.tagNameCheck(e))&&(we.attributeNameCheck instanceof RegExp&&A(we.attributeNameCheck,t)||we.attributeNameCheck instanceof Function&&we.attributeNameCheck(t))||"is"===t&&we.allowCustomizedBuiltInElements&&(we.tagNameCheck instanceof RegExp&&A(we.tagNameCheck,n)||we.tagNameCheck instanceof Function&&we.tagNameCheck(n))))return!1}else if(Ve[t]);else if(A(_e,g(n,Ee,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==T(n,"data:")||!Ke[e]){if(Ce&&!A(ye,g(n,Ee,"")));else if(n)return!1}else;return!0},wt=function(e){return"annotation-xml"!==e&&h(e,Ae)},Ot=function(e){bt(pe.beforeSanitizeAttributes,e,null);const{attributes:t}=e;if(!t)return;const n={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:Ne,forceKeepAttr:void 0};let r=t.length;for(;r--;){const i=t[r],{name:a,namespaceURI:l,value:c}=i,s=ut(a);let p="value"===a?c:y(c);if(n.attrName=s,n.attrValue=p,n.keepAttr=!0,n.forceKeepAttr=void 0,bt(pe.uponSanitizeAttribute,e,n),p=n.attrValue,!We||"id"!==s&&"name"!==s||(yt(a,e),p="user-content-"+p),ke&&A(/((--!?|])>)|<\/(style|title)/i,p)){yt(a,e);continue}if(n.forceKeepAttr)continue;if(yt(a,e),!n.keepAttr)continue;if(!xe&&A(/\/>/i,p)){yt(a,e);continue}Me&&u([fe,de,he],(e=>{p=g(p,e," ")}));const f=ut(e.nodeName);if(Rt(f,s,p)){if(ie&&"object"==typeof G&&"function"==typeof G.getAttributeType)if(l);else switch(G.getAttributeType(f,s)){case"TrustedHTML":p=ie.createHTML(p);break;case"TrustedScriptURL":p=ie.createScriptURL(p)}try{l?e.setAttributeNS(l,a,p):e.setAttribute(a,p),_t(e)?Tt(e):m(o.removed)}catch(e){}}}bt(pe.afterSanitizeAttributes,e,null)},Dt=function e(t){let n=null;const o=At(t);for(bt(pe.beforeSanitizeShadowDOM,t,null);n=o.nextNode();)bt(pe.uponSanitizeShadowNode,n,null),Nt(n)||(n.content instanceof s&&e(n.content),Ot(n));bt(pe.afterSanitizeShadowDOM,t,null)};return o.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=null,r=null,i=null,l=null;if(nt=!e,nt&&(e="\x3c!--\x3e"),"string"!=typeof e&&!St(e)){if("function"!=typeof e.toString)throw _("toString is not a function");if("string"!=typeof(e=e.toString()))throw _("dirty is not a string, aborting")}if(!o.isSupported)return e;if(Ue||dt(t),o.removed=[],"string"==typeof e&&(Ye=!1),Ye){if(e.nodeName){const t=ut(e.nodeName);if(!Se[t]||Oe[t])throw _("root node is forbidden and cannot be sanitized in-place")}}else if(e instanceof b)n=Et("\x3c!----\x3e"),r=n.ownerDocument.importNode(e,!0),r.nodeType===V&&"BODY"===r.nodeName||"HTML"===r.nodeName?n=r:n.appendChild(r);else{if(!Pe&&!Me&&!Ie&&-1===e.indexOf("<"))return ie&&Fe?ie.createHTML(e):e;if(n=Et(e),!n)return Pe?null:Fe?ae:""}n&&ze&&Tt(n.firstChild);const c=At(Ye?e:n);for(;i=c.nextNode();)Nt(i)||(i.content instanceof s&&Dt(i.content),Ot(i));if(Ye)return e;if(Pe){if(He)for(l=se.call(n.ownerDocument);n.firstChild;)l.appendChild(n.firstChild);else l=n;return(Ne.shadowroot||Ne.shadowrootmode)&&(l=me.call(a,l,!0)),l}let m=Ie?n.outerHTML:n.innerHTML;return Ie&&Se["!doctype"]&&n.ownerDocument&&n.ownerDocument.doctype&&n.ownerDocument.doctype.name&&A(q,n.ownerDocument.doctype.name)&&(m="<!DOCTYPE "+n.ownerDocument.doctype.name+">\n"+m),Me&&u([fe,de,he],(e=>{m=g(m,e," ")})),ie&&Fe?ie.createHTML(m):m},o.setConfig=function(){dt(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),Ue=!0},o.clearConfig=function(){mt=null,Ue=!1},o.isValidAttribute=function(e,t,n){mt||dt({});const o=ut(e),r=ut(t);return Rt(o,r,n)},o.addHook=function(e,t){"function"==typeof t&&p(pe[e],t)},o.removeHook=function(e){return m(pe[e])},o.removeHooks=function(e){pe[e]=[]},o.removeAllHooks=function(){pe={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},o}();return ne}));
 //# sourceMappingURL=purify.min.js.map
diff --git a/package-lock.json b/package-lock.json
index f4fe14a8..5b18f3f9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "dompurify",
-  "version": "3.2.1",
+  "version": "3.2.2",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "dompurify",
-      "version": "3.2.1",
+      "version": "3.2.2",
       "license": "(MPL-2.0 OR Apache-2.0)",
       "devDependencies": {
         "@babel/core": "^7.17.8",
@@ -16,6 +16,7 @@
         "@rollup/plugin-replace": "^6.0.1",
         "@rollup/plugin-terser": "^0.4.4",
         "@types/estree": "^1.0.0",
+        "@types/node": "^16.18.120",
         "cross-env": "^7.0.3",
         "eslint-config-prettier": "^8.5.0",
         "eslint-plugin-prettier": "^4.0.0",
@@ -2229,7 +2230,9 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "14.14.13",
+      "version": "16.18.120",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.120.tgz",
+      "integrity": "sha512-Dmi4bhZ7CHyD4sv4awCZx9RBxWOXSejxTF6B5WQ5UzfLcyEg7JqdDDsjvdMRYES9EcTWHlHZe01PInSj18yP2A==",
       "dev": true,
       "license": "MIT"
     },
@@ -12538,7 +12541,9 @@
       "dev": true
     },
     "@types/node": {
-      "version": "14.14.13",
+      "version": "16.18.120",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.120.tgz",
+      "integrity": "sha512-Dmi4bhZ7CHyD4sv4awCZx9RBxWOXSejxTF6B5WQ5UzfLcyEg7JqdDDsjvdMRYES9EcTWHlHZe01PInSj18yP2A==",
       "dev": true
     },
     "@types/normalize-package-data": {
diff --git a/package.json b/package.json
index 766bf165..b851b733 100644
--- a/package.json
+++ b/package.json
@@ -7,10 +7,10 @@
     "commit-amend-build": "scripts/commit-amend-build.sh",
     "prebuild": "rimraf dist/**",
     "dev": "cross-env NODE_ENV=development BABEL_ENV=rollup rollup -w -c -o dist/purify.js",
-    "build": "run-s build:types build:rollup build:fix-cjs-types build:cleanup",
+    "build": "run-s build:types build:rollup build:fix-types build:cleanup",
     "build:types": "tsc --outDir dist/types --declaration --emitDeclarationOnly",
     "build:rollup": "rollup -c",
-    "build:fix-cjs-types": "node ./scripts/fix-cjs-types.js",
+    "build:fix-types": "node ./scripts/fix-types.js",
     "build:umd": "rollup -c -f umd -o dist/purify.js",
     "build:umd:min": "rollup -c -f umd -o dist/purify.min.js -p terser",
     "build:es": "rollup -c -f es -o dist/purify.es.mjs",
@@ -103,6 +103,7 @@
     "@rollup/plugin-replace": "^6.0.1",
     "@rollup/plugin-terser": "^0.4.4",
     "@types/estree": "^1.0.0",
+    "@types/node": "^16.18.120",
     "cross-env": "^7.0.3",
     "eslint-config-prettier": "^8.5.0",
     "eslint-plugin-prettier": "^4.0.0",
@@ -135,7 +136,7 @@
   },
   "name": "dompurify",
   "description": "DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG. It's written in JavaScript and works in all modern browsers (Safari, Opera (15+), Internet Explorer (10+), Firefox and Chrome - as well as almost anything else using Blink or WebKit). DOMPurify is written by security people who have vast background in web attacks and XSS. Fear not.",
-  "version": "3.2.1",
+  "version": "3.2.2",
   "directories": {
     "test": "test"
   },
diff --git a/scripts/fix-cjs-types.js b/scripts/fix-cjs-types.js
deleted file mode 100644
index a544e078..00000000
--- a/scripts/fix-cjs-types.js
+++ /dev/null
@@ -1,31 +0,0 @@
-const fs = require('node:fs/promises');
-const path = require('node:path');
-
-(async () => {
-  // Note that this script is intended to run on the type declaration file that is
-  // output by Rollup, and not the type declaration file generated from TypeScript.
-  let fileName = path.resolve(__dirname, '../dist/purify.cjs.d.ts');
-  let types = await fs.readFile(fileName, { encoding: 'utf-8' });
-
-  // DOMPurify is exported as a default export, but rollup changes
-  // it to be assigned to `module.exports`. We need to change the
-  // type declarations to match what rollup changed it to. Remove
-  // the "default" export from the `export { ... }` statement.
-  let fixed = types.replace(', _default as default', '');
-
-  // Verify that we actually removed something in case the
-  // type declarations are different to what we expected.
-  if (fixed === types) {
-    throw new Error('Failed to fix CommonJS type declarations.');
-  }
-
-  // Append `export = _default;` to match the
-  // `module.exports = DOMPurify` statement
-  // that Rollup creates.
-  fixed += '\n// @ts-ignore\nexport = _default;\n';
-
-  await fs.writeFile(fileName, fixed);
-})().catch((ex) => {
-  console.error(ex);
-  process.exitCode = 1;
-});
diff --git a/scripts/fix-types.js b/scripts/fix-types.js
new file mode 100644
index 00000000..02aa4eeb
--- /dev/null
+++ b/scripts/fix-types.js
@@ -0,0 +1,58 @@
+// @ts-check
+
+const fs = require('node:fs/promises');
+const path = require('node:path');
+
+(async () => {
+  // Note that this script is intended to run on the type declaration file that is
+  // output by Rollup, and not the type declaration file generated from TypeScript.
+  await fixCjsTypes(path.resolve(__dirname, '../dist/purify.cjs.d.ts'));
+  await fixEsmTypes(path.resolve(__dirname, '../dist/purify.es.d.mts'));
+})().catch((ex) => {
+  console.error(ex);
+  process.exitCode = 1;
+});
+
+/**
+ * Fixes the CommonJS type declarations file.
+ * @param {string} fileName
+ */
+async function fixCjsTypes(fileName) {
+  let types = await fs.readFile(fileName, { encoding: 'utf-8' });
+
+  // DOMPurify is exported as a default export, but rollup changes
+  // it to be assigned to `module.exports`. We need to change the
+  // type declarations to match what rollup changed it to. Remove
+  // the "default" export from the `export { ... }` statement.
+  let fixed = types.replace(', _default as default', '');
+
+  // Verify that we actually removed something in case the
+  // type declarations are different to what we expected.
+  if (fixed === types) {
+    throw new Error('Failed to fix CommonJS type declarations.');
+  }
+
+  // Append `export = _default;` to match the `module.exports = DOMPurify`
+  // statement that Rollup creates. This can cause compilation errors
+  // for certain configurations, so add a `@ts-ignore` comment before it.
+  fixed += '\n// @ts-ignore\nexport = _default;\n';
+
+  await fs.writeFile(fileName, addTrustedTypesReference(fixed));
+}
+
+/**
+ * Fixes the ESM type declarations file.
+ * @param {string} fileName
+ */
+async function fixEsmTypes(fileName) {
+  let types = await fs.readFile(fileName, { encoding: 'utf-8' });
+  await fs.writeFile(fileName, addTrustedTypesReference(types));
+}
+
+function addTrustedTypesReference(types) {
+  // We need to tell TypeScript that we use the type declarations from
+  // `trusted-types` so that it ends up in our type declaration type).
+  // Without this, the references to trusted-types in the type declaration
+  // file can cause compilation errors for certain configurations.
+  return `/// <reference types="trusted-types" />\n${types}`;
+}
diff --git a/src/purify.ts b/src/purify.ts
index 4143aa27..255d7ec1 100644
--- a/src/purify.ts
+++ b/src/purify.ts
@@ -99,6 +99,20 @@ const _createTrustedTypesPolicy = function (
   }
 };
 
+const _createHooksMap = function (): HooksMap {
+  return {
+    afterSanitizeAttributes: [],
+    afterSanitizeElements: [],
+    afterSanitizeShadowDOM: [],
+    beforeSanitizeAttributes: [],
+    beforeSanitizeElements: [],
+    beforeSanitizeShadowDOM: [],
+    uponSanitizeAttribute: [],
+    uponSanitizeElement: [],
+    uponSanitizeShadowNode: [],
+  };
+};
+
 function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
   const DOMPurify: DOMPurify = (root: WindowLike) => createDOMPurify(root);
 
@@ -167,7 +181,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
   } = document;
   const { importNode } = originalDocument;
 
-  let hooks = {};
+  let hooks = _createHooksMap();
 
   /**
    * Expose whether this browser supports running the full DOMPurify.
@@ -842,8 +856,8 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
 
     element.removeAttribute(name);
 
-    // We void attribute values for unremovable "is"" attributes
-    if (name === 'is' && !ALLOWED_ATTR[name]) {
+    // We void attribute values for unremovable "is" attributes
+    if (name === 'is') {
       if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
         try {
           _forceRemove(element);
@@ -982,45 +996,15 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
     return typeof Node === 'function' && value instanceof Node;
   };
 
-  // The following overloads of `_executeHook` add type-safety to the callers,
-  // ensuring that the caller provides the correct `data` parameter.
-
-  /**
-   * _executeHook
-   * Execute user configurable hooks
-   *
-   * @param entryPoint Name of the hook's entry point
-   * @param currentNode node to work on with the hook
-   * @param data additional hook parameters
-   */
-  function _executeHook(
-    entryPoint: BasicHookName,
-    currentNode: Node,
-    data: null
-  ): void;
-
-  function _executeHook(
-    entryPoint: UponSanitizeElementHookName,
-    currentNode: Node,
-    data: UponSanitizeElementHookEvent
-  ): void;
-
-  function _executeHook(
-    entryPoint: UponSanitizeAttributeHookName,
-    currentNode: Node,
-    data: UponSanitizeAttributeHookEvent
-  ): void;
-
-  function _executeHook(
-    entryPoint: HookName,
-    currentNode: Node,
-    data: UponSanitizeAttributeHookEvent | UponSanitizeElementHookEvent | null
-  ): void {
-    if (!hooks[entryPoint]) {
-      return;
-    }
-
-    arrayForEach(hooks[entryPoint], (hook) => {
+  function _executeHooks<
+    T extends
+      | NodeHook
+      | ElementHook
+      | DocumentFragmentHook
+      | UponSanitizeElementHook
+      | UponSanitizeAttributeHook
+  >(hooks: T[], currentNode: Parameters<T>[0], data: Parameters<T>[1]): void {
+    arrayForEach(hooks, (hook) => {
       hook.call(DOMPurify, currentNode, data, CONFIG);
     });
   }
@@ -1038,7 +1022,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
     let content = null;
 
     /* Execute a hook if present */
-    _executeHook('beforeSanitizeElements', currentNode, null);
+    _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
 
     /* Check if element is clobbered or can clobber */
     if (_isClobbered(currentNode)) {
@@ -1050,7 +1034,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
     const tagName = transformCaseFunc(currentNode.nodeName);
 
     /* Execute a hook if present */
-    _executeHook('uponSanitizeElement', currentNode, {
+    _executeHooks(hooks.uponSanitizeElement, currentNode, {
       tagName,
       allowedTags: ALLOWED_TAGS,
     });
@@ -1154,7 +1138,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
     }
 
     /* Execute a hook if present */
-    _executeHook('afterSanitizeElements', currentNode, null);
+    _executeHooks(hooks.afterSanitizeElements, currentNode, null);
 
     return false;
   };
@@ -1284,7 +1268,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
    */
   const _sanitizeAttributes = function (currentNode: Element): void {
     /* Execute a hook if present */
-    _executeHook('beforeSanitizeAttributes', currentNode, null);
+    _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
 
     const { attributes } = currentNode;
 
@@ -1315,7 +1299,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
       hookEvent.attrValue = value;
       hookEvent.keepAttr = true;
       hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
-      _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
+      _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
       value = hookEvent.attrValue;
 
       /* Full DOM Clobbering protection via namespace isolation,
@@ -1412,7 +1396,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
     }
 
     /* Execute a hook if present */
-    _executeHook('afterSanitizeAttributes', currentNode, null);
+    _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
   };
 
   /**
@@ -1425,11 +1409,11 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
     const shadowIterator = _createNodeIterator(fragment);
 
     /* Execute a hook if present */
-    _executeHook('beforeSanitizeShadowDOM', fragment, null);
+    _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
 
     while ((shadowNode = shadowIterator.nextNode())) {
       /* Execute a hook if present */
-      _executeHook('uponSanitizeShadowNode', shadowNode, null);
+      _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
 
       /* Sanitize tags and elements */
       if (_sanitizeElements(shadowNode)) {
@@ -1446,7 +1430,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
     }
 
     /* Execute a hook if present */
-    _executeHook('afterSanitizeShadowDOM', fragment, null);
+    _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
   };
 
   // eslint-disable-next-line complexity
@@ -1652,24 +1636,19 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
       return;
     }
 
-    hooks[entryPoint] = hooks[entryPoint] || [];
     arrayPush(hooks[entryPoint], hookFunction);
   };
 
   DOMPurify.removeHook = function (entryPoint) {
-    if (hooks[entryPoint]) {
-      return arrayPop(hooks[entryPoint]);
-    }
+    return arrayPop(hooks[entryPoint]);
   };
 
   DOMPurify.removeHooks = function (entryPoint) {
-    if (hooks[entryPoint]) {
-      hooks[entryPoint] = [];
-    }
+    hooks[entryPoint] = [];
   };
 
   DOMPurify.removeAllHooks = function () {
-    hooks = {};
+    hooks = _createHooksMap();
   };
 
   return DOMPurify;
@@ -1780,7 +1759,26 @@ export interface DOMPurify {
    * @param entryPoint entry point for the hook to add
    * @param hookFunction function to execute
    */
-  addHook(entryPoint: BasicHookName, hookFunction: Hook): void;
+  addHook(entryPoint: BasicHookName, hookFunction: NodeHook): void;
+
+  /**
+   * Adds a DOMPurify hook.
+   *
+   * @param entryPoint entry point for the hook to add
+   * @param hookFunction function to execute
+   */
+  addHook(entryPoint: ElementHookName, hookFunction: ElementHook): void;
+
+  /**
+   * Adds a DOMPurify hook.
+   *
+   * @param entryPoint entry point for the hook to add
+   * @param hookFunction function to execute
+   */
+  addHook(
+    entryPoint: DocumentFragmentHookName,
+    hookFunction: DocumentFragmentHook
+  ): void;
 
   /**
    * Adds a DOMPurify hook.
@@ -1811,7 +1809,27 @@ export interface DOMPurify {
    * @param entryPoint entry point for the hook to remove
    * @returns removed(popped) hook
    */
-  removeHook(entryPoint: BasicHookName): Hook | undefined;
+  removeHook(entryPoint: BasicHookName): NodeHook | undefined;
+
+  /**
+   * Remove a DOMPurify hook at a given entryPoint
+   * (pops it from the stack of hooks if more are present)
+   *
+   * @param entryPoint entry point for the hook to remove
+   * @returns removed(popped) hook
+   */
+  removeHook(entryPoint: ElementHookName): ElementHook | undefined;
+
+  /**
+   * Remove a DOMPurify hook at a given entryPoint
+   * (pops it from the stack of hooks if more are present)
+   *
+   * @param entryPoint entry point for the hook to remove
+   * @returns removed(popped) hook
+   */
+  removeHook(
+    entryPoint: DocumentFragmentHookName
+  ): DocumentFragmentHook | undefined;
 
   /**
    * Remove a DOMPurify hook at a given entryPoint
@@ -1876,27 +1894,54 @@ export interface RemovedAttribute {
 type BasicHookName =
   | 'beforeSanitizeElements'
   | 'afterSanitizeElements'
-  | 'beforeSanitizeAttributes'
-  | 'afterSanitizeAttributes'
+  | 'uponSanitizeShadowNode';
+type ElementHookName = 'beforeSanitizeAttributes' | 'afterSanitizeAttributes';
+type DocumentFragmentHookName =
   | 'beforeSanitizeShadowDOM'
-  | 'uponSanitizeShadowNode'
   | 'afterSanitizeShadowDOM';
-
 type UponSanitizeElementHookName = 'uponSanitizeElement';
 type UponSanitizeAttributeHookName = 'uponSanitizeAttribute';
 
+interface HooksMap {
+  beforeSanitizeElements: NodeHook[];
+  afterSanitizeElements: NodeHook[];
+  beforeSanitizeShadowDOM: DocumentFragmentHook[];
+  uponSanitizeShadowNode: NodeHook[];
+  afterSanitizeShadowDOM: DocumentFragmentHook[];
+  beforeSanitizeAttributes: ElementHook[];
+  afterSanitizeAttributes: ElementHook[];
+  uponSanitizeElement: UponSanitizeElementHook[];
+  uponSanitizeAttribute: UponSanitizeAttributeHook[];
+}
+
 export type HookName =
   | BasicHookName
+  | ElementHookName
+  | DocumentFragmentHookName
   | UponSanitizeElementHookName
   | UponSanitizeAttributeHookName;
 
-export type Hook = (
+export type NodeHook = (
   this: DOMPurify,
   currentNode: Node,
   hookEvent: null,
   config: Config
 ) => void;
 
+export type ElementHook = (
+  this: DOMPurify,
+  currentNode: Element,
+  hookEvent: null,
+  config: Config
+) => void;
+
+export type DocumentFragmentHook = (
+  this: DOMPurify,
+  currentNode: DocumentFragment,
+  hookEvent: null,
+  config: Config
+) => void;
+
 export type UponSanitizeElementHook = (
   this: DOMPurify,
   currentNode: Node,
@@ -1906,7 +1951,7 @@ export type UponSanitizeElementHook = (
 
 export type UponSanitizeAttributeHook = (
   this: DOMPurify,
-  currentNode: Node,
+  currentNode: Element,
   hookEvent: UponSanitizeAttributeHookEvent,
   config: Config
 ) => void;
diff --git a/src/regexp.ts b/src/regexp.ts
index 73786298..2d15f46a 100644
--- a/src/regexp.ts
+++ b/src/regexp.ts
@@ -3,7 +3,7 @@ import { seal } from './utils.js';
 // eslint-disable-next-line unicorn/better-regex
 export const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
 export const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
-export const TMPLIT_EXPR = seal(/\${[\w\W]*}/gm);
+export const TMPLIT_EXPR = seal(/\$\{[\w\W]*}/gm); // eslint-disable-line unicorn/better-regex
 export const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape
 export const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
 export const IS_ALLOWED_URI = seal(
diff --git a/website/index.html b/website/index.html
index 63636101..1a357a8c 100644
--- a/website/index.html
+++ b/website/index.html
@@ -2,7 +2,7 @@
 <html lang="en">
     <head>
         <meta charset="UTF-8">
-        <title>DOMPurify 3.2.1 "Typographer"</title>
+        <title>DOMPurify 3.2.2 "Monospace"</title>
         <script src="../dist/purify.min.js"></script>
         <!-- we don't actually need it - just to demo and test the $(html) sanitation -->
         <script src="//code.jquery.com/jquery-3.2.0.min.js"></script>
@@ -23,7 +23,7 @@
         </script>
     </head>
     <body>
-        <h4>DOMPurify 3.2.1 "Typographer"</h4>
+        <h4>DOMPurify 3.2.2 "Monospace"</h4>
         <p>
             <a href="http://badge.fury.io/js/dompurify" rel="nofollow"><img alt="npm version" src="https://badge.fury.io/js/dompurify.svg"></a>  
             <a target="_blank" rel="noopener noreferrer" href="https://github.com/cure53/DOMPurify/workflows/Build%20and%20Test/badge.svg?branch=main"><img src="https://github.com/cure53/DOMPurify/workflows/Build%20and%20Test/badge.svg?branch=main" alt="Build and Test"></a>  


Test patch: