Release notes · September 12, 2026

RichTextEditor 2.9.0

The editor now works under a strict Content-Security-Policy. Under style-src 'self' the browser was discarding the rules of every <style>element the editor injects, so the editing surface lost its typography, gutters, table borders and cell-selection highlight, and 41 plugin UIs rendered unstyled — with nothing thrown and nothing obviously broken. Core build 589,759 → 592,031.

Latest
2.9.0

The editing surface was unstyled under a strict CSP Fixed

style-src governs <style> elements. When a policy omits 'unsafe-inline', the element stays in the DOM and its rules are dropped. Nothing throws, the toolbar and panels still render, and a visual check passes — the tell is document.styleSheets.length disagreeing with the number of <style> tags.

Two core stylesheets were affected: __rte_base_styles (body typography and the editing gutters) and __rte_default_styles (table borders, multi-cell selection highlight, find highlight, inline math). Both apply again.

editor.ensureStyleSheetLive() for plugins that inject CSS New

var el = doc.createElement("style");
el.textContent = css;
doc.head.appendChild(el);                 // unchanged - keep your element
editor.ensureStyleSheetLive(doc, el, "my-plugin-styles", css);

The call does nothing unless the policy actually emptied the element, in which case it re-injects the same CSS through the CSSOM, which CSP does not intercept. 41 shipped plugins now do this.

Keeping the <style>element as the primary path is the important part, and it is why this is not simply “put the rules in adoptedStyleSheets”. An adopted stylesheet sorts after every stylesheet in the document, so routing plugin CSS there unconditionally would let it override your own contentCssUrl— breaking content styling for everyone who has no CSP at all. The element keeps each injector’s deliberate place in the cascade; the CSSOM path is used only where the alternative is no rules.

editor.injectStyleSheet(doc, id, csstext)is also public, for a caller with no element to preserve. Re-calling either with the same id and different CSS updates the existing sheet, so plugins that rewrite their rules as settings change keep working. · Plugin CSS guide

Notes

  • No behaviour change without a CSP. Measured on one page with and without a policy: with no CSP all 15 stylesheets in an 18-plugin sample live in their <style> elements and adoptedStyleSheets is empty, exactly as before. Under style-src 'self' all 15 apply through the CSSOM, with identical computed values.
  • The sheet registry is per-document. Two editors on one page have two editable documents, and the editable document is replaced when its iframe reloads, so a registry keyed by id alone would tell the second document its stylesheet was already installed and inject nothing.
  • Known gap: two plugins were missed. rubyannotation.js and stateinspector.js inject a <style> without the fallback, so they still render unstyled under a strict style-src. Found after release by auditing the full plugin bundle rather than a sample; fixed for the next core build.
  • shadowdom.js is deliberately unconverted: it clones style nodes into a shadow root, which has its own adoptedStyleSheets and needs its own design.
  • One style attribute write still needs 'unsafe-inline'. Removing it changes the HTML the editor saves for every customer, so it is a separate decision and is not in this release.

Install or upgrade

Grab the latest via npm:

npm install @richscripts/richtexteditor@latest

Feedback & issues: support@richtexteditor.com