Skip to content

[lexical][lexical-code-core][lexical-list][lexical-table][lexical-yjs] Refactor: make runtime style updates CSP-safe#8372

Merged
etrepum merged 7 commits intofacebook:mainfrom
holly-agyei:feat-8363-csp-safe-styles
Apr 22, 2026
Merged

[lexical][lexical-code-core][lexical-list][lexical-table][lexical-yjs] Refactor: make runtime style updates CSP-safe#8372
etrepum merged 7 commits intofacebook:mainfrom
holly-agyei:feat-8363-csp-safe-styles

Conversation

@holly-agyei
Copy link
Copy Markdown
Contributor

Description

This replaces runtime style string writes in Lexical's DOM update paths with property-based style updates.

  • add a shared helper to parse stored style strings and apply or remove individual properties
  • update text, code, list, table, yjs cursor, and playground paths that previously used cssText or setAttribute('style', ...)
  • add unit coverage for style creation and update behavior

Closes #8363

Test plan

Before

  • Runtime DOM updates used .style.cssText and setAttribute('style', ...) in editor paths.

After

  • env COREPACK_HOME=/tmp/corepack corepack pnpm run test-unit
  • env COREPACK_HOME=/tmp/corepack corepack pnpm run tsc
  • env COREPACK_HOME=/tmp/corepack corepack pnpm --filter @lexical/devtools run compile
  • env COREPACK_HOME=/tmp/corepack corepack pnpm run build-types
  • env COREPACK_HOME=/tmp/corepack corepack pnpm run lint
  • env COREPACK_HOME=/tmp/corepack corepack pnpm run prettier

Copilot AI review requested due to automatic review settings April 19, 2026 06:21
@meta-cla
Copy link
Copy Markdown

meta-cla Bot commented Apr 19, 2026

Hi @holly-agyei!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment Apr 22, 2026 7:31am
lexical-playground Ready Ready Preview, Comment Apr 22, 2026 7:31am

Request Review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors Lexical’s runtime DOM style updates to be CSP-safe by replacing cssText / setAttribute('style', ...) usage with CSSOM property-based updates, and introduces shared utilities + tests to support the new approach.

Changes:

  • Add shared/setDOMStyle helpers to parse stored style strings and apply/remove individual properties via CSSStyleDeclaration.setProperty/removeProperty.
  • Update multiple packages (core text/code/list/table/yjs/playground/utils/clipboard) to avoid runtime style string writes.
  • Add unit tests covering style parsing and DOM application/update behavior.

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tsconfig.json Adds TS path mapping for shared/setDOMStyle.
tsconfig.build.json Adds build TS path mapping for shared/setDOMStyle.
packages/shared/src/setDOMStyle.ts Introduces shared CSP-safe style parsing/application helpers.
packages/shared/src/tests/unit/setDOMStyle.test.ts Adds unit coverage for parsing and applying/removing styles.
packages/lexical/src/nodes/tests/unit/LexicalTextNode.test.tsx Adds tests for CSP-safe style application and updates on TextNode.
packages/lexical/src/nodes/LexicalTextNode.ts Replaces cssText usage with setDOMStyleFromCSS.
packages/lexical/src/nodes/LexicalElementNode.ts Replaces cssText usage with per-property setProperty calls.
packages/lexical-yjs/src/SyncCursors.ts Replaces cssText usage with setDOMStyleObject for cursor UI styling.
packages/lexical-utils/src/index.ts Replaces cssText usage with direct style property assignments.
packages/lexical-table/src/LexicalTableNode.ts Replaces cssText usage with setDOMStyleFromCSS / direct property assignment.
packages/lexical-playground/src/plugins/CommentPlugin/index.tsx Replaces cssText usage with direct style property assignments for highlights.
packages/lexical-playground/src/nodes/MentionNode.ts Replaces cssText usage with a direct backgroundColor assignment.
packages/lexical-playground/split/index.html Replaces cssText usage with Object.assign style property updates.
packages/lexical-list/src/LexicalListItemNode.ts Replaces cssText/removeAttribute('style') path with setDOMStyleFromCSS.
packages/lexical-devtools/tsconfig.json Adds TS path mapping for shared/setDOMStyle.
packages/lexical-code-core/src/tests/unit/CodeNode.test.ts Adds tests for CSP-safe style application/update/export on CodeNode.
packages/lexical-code-core/src/CodeNode.ts Replaces setAttribute('style', ...) with setDOMStyleFromCSS in create/update/export.
packages/lexical-clipboard/src/clipboard.ts Replaces cssText usage with direct style property assignments.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/shared/src/setDOMStyle.ts Outdated
Comment thread packages/shared/src/setDOMStyle.ts Outdated
Comment thread packages/shared/src/setDOMStyle.ts Outdated
@holly-agyei holly-agyei force-pushed the feat-8363-csp-safe-styles branch from f906c71 to b3b1107 Compare April 19, 2026 06:40
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 19, 2026
@holly-agyei holly-agyei force-pushed the feat-8363-csp-safe-styles branch from b3b1107 to d39671e Compare April 19, 2026 06:44
@holly-agyei holly-agyei changed the title [lexical][lexical-code-core][lexical-list][lexical-table][lexical-yjs] Bug Fix: make runtime style updates CSP-safe [lexical][lexical-code-core][lexical-list][lexical-table][lexical-yjs] Refactor: make runtime style updates CSP-safe Apr 19, 2026
Comment thread packages/shared/src/setDOMStyle.ts Outdated
@etrepum etrepum added the extended-tests Run extended e2e tests on a PR label Apr 19, 2026
@holly-agyei holly-agyei requested a review from ivailop7 as a code owner April 20, 2026 02:30
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 20, 2026

Deployment failed with the following error:

Failed to create deployment for team_QNIQyH5DJlqUjeCckcCcBEjE in project prj_MRaMO31eFIkY61SBqqPDR2Ojy8qP: FetchError: request to https://76.76.21.112/v13/now/deployments?ownerId=team_QNIQyH5DJlqUjeCckcCcBEjE&projectId=prj_MRaMO31eFIkY61SBqqPDR2Ojy8qP&skipAutoDetectionConfirmation=1&teamId=team_QNIQyH5DJlqUjeCckcCcBEjE&traceCarrier=%7B%22ot-baggage-webhookAt%22%3A%221776652242808%22%2C%22ot-baggage-senderUsername%22%3A%22gh.holly-agyei%22%2C%22x-datadog-trace-id%22%3A%222387447889907019158%22%2C%22x-datadog-parent-id%22%3A%221732396590902269054%22%2C%22x-datadog-sampling-priority%22%3A%222%22%2C%22x-datadog-tags%22%3A%22_dd.p.tid%3D69e58fd200000000%2C_dd.p.ksr%3D1%2C_dd.p.dm%3D-3%22%2C%22traceparent%22%3A%2200-69e58fd2000000002121eb34e5a9ed96-180ab58552d3d47e-01%22%2C%22tracestate%22%3A%22dd%3Dt.ksr%3A1%3Bt.tid%3A69e58fd200000000%3Bt.dm%3A-3%3Bs%3A2%3Bp%3A180ab58552d3d47e%22%7D failed, reason: socket hang up

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 20, 2026

Deployment failed with the following error:

Failed to create deployment for team_QNIQyH5DJlqUjeCckcCcBEjE in project prj_ea82vLj684XzSRdeO7JSd0QNkaws: FetchError: request to https://76.76.21.112/v13/now/deployments?ownerId=team_QNIQyH5DJlqUjeCckcCcBEjE&projectId=prj_ea82vLj684XzSRdeO7JSd0QNkaws&skipAutoDetectionConfirmation=1&teamId=team_QNIQyH5DJlqUjeCckcCcBEjE&traceCarrier=%7B%22ot-baggage-webhookAt%22%3A%221776652242808%22%2C%22ot-baggage-senderUsername%22%3A%22gh.holly-agyei%22%2C%22x-datadog-trace-id%22%3A%222387447889907019158%22%2C%22x-datadog-parent-id%22%3A%224122743267355693851%22%2C%22x-datadog-sampling-priority%22%3A%222%22%2C%22x-datadog-tags%22%3A%22_dd.p.tid%3D69e58fd200000000%2C_dd.p.ksr%3D1%2C_dd.p.dm%3D-3%22%2C%22traceparent%22%3A%2200-69e58fd2000000002121eb34e5a9ed96-3936ed288ede931b-01%22%2C%22tracestate%22%3A%22dd%3Dt.ksr%3A1%3Bt.tid%3A69e58fd200000000%3Bt.dm%3A-3%3Bs%3A2%3Bp%3A3936ed288ede931b%22%7D failed, reason: socket hang up

Comment thread packages/lexical-selection/src/utils.ts Outdated
Copy link
Copy Markdown
Collaborator

@etrepum etrepum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's definitely getting close but the change in the caching behavior means that a very thorough audit should be done - possibly adding a different (better) cache.

Comment thread packages/lexical-selection/src/lexical-node.ts Outdated
Comment thread packages/lexical-selection/src/lexical-node.ts Outdated
Comment thread packages/lexical-selection/src/index.ts Outdated
Comment thread packages/lexical-selection/src/lexical-node.ts Outdated
Comment thread packages/lexical-selection/src/utils.ts Outdated
Comment thread packages/lexical-selection/src/constants.ts
@holly-agyei
Copy link
Copy Markdown
Contributor Author

@etrepum
Thanks for pointing these out to look at.
I have gone through the reviews to fix them. Kindly take a look

Copy link
Copy Markdown
Collaborator

@etrepum etrepum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like before I think because this changes the cache fundamentally I think it needs a very close audit. It's also not clear that 100 is a good size for this cache, or even if the cache is needed at all. There isn't any sort of easy to run benchmark that would make it easy to make that kind of decision. It's likely that the caching would be better to do closer to the nodes that the styles are for, if caching is useful, but that would also take careful consideration to avoid causing problems with collab.

@holly-agyei
Copy link
Copy Markdown
Contributor Author

Like before I think because this changes the cache fundamentally I think it needs a very close audit. It's also not clear that 100 is a good size for this cache, or even if the cache is needed at all. There isn't any sort of easy to run benchmark that would make it easy to make that kind of decision. It's likely that the caching would be better to do closer to the nodes that the styles are for, if caching is useful, but that would also take careful consideration to avoid causing problems with collab.

Thanks, I took a closer pass on the cache behavior with performance as the primary goal and changed direction.

I removed the global parser cache entirely. After auditing it more closely, I didn’t think I could justify that policy in this PR: the 100 entry size was arbitrary, the hit rate sounds quite unclear(it changes global parser behavior across editors), and because callers still need fresh objects, it would still allocate on cache hits. So it added complexity without a strong enough performance case.

Instead I moved the optimization closer to the actual hot paths:

  • getStyleObjectFromCSS is pure again and returns a fresh object.
  • setDOMStyleFromCSS now iterates parsed declarations directly when applying/removing DOM styles, so that path avoids building intermediate style objects.
  • ListItemNode now skips marker style work entirely when __textStyle is unchanged, which felt like a better place to capture repeated work than a global parser cache.

The tradeoff I chose was to avoid a broad cache-policy change and keep the parser semantics simple, while still making the important runtime paths cheaper. That seemed like the safer performance-oriented shape here, especially without a benchmark that would justify a specific cache strategy or size.

Copy link
Copy Markdown
Collaborator

@etrepum etrepum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's optimize this parser implementation to minimize the impact of not having a cache

I think the place that might be impacted the most by not having a cache is code that uses $getSelectionStyleValueForProperty repeatedly for the same selection, but there are other ways to optimize that than a global cache.

Comment thread packages/lexical/src/index.ts
Comment thread packages/lexical/src/utils/setDOMStyle.ts Outdated
Comment thread packages/lexical/src/utils/setDOMStyle.ts Outdated
Comment thread packages/lexical/src/utils/setDOMStyle.ts Outdated
Comment thread packages/lexical/src/index.ts Outdated
Comment on lines +9 to +13
import {
getStyleObjectFromCSS as getStyleObjectFromCSS_,
setDOMStyleFromCSS as setDOMStyleFromCSS_,
setDOMStyleObject as setDOMStyleObject_,
} from './utils/setDOMStyle';
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This technique only needs to be done when deprecating re-exports, don't do it here. Put the API docs with the implementation of the function and simply export these directly.

Comment thread packages/lexical/src/index.ts Outdated
Comment on lines +404 to +427
/**
* Parses inline CSS text into an object that is compatible with
* `CSSStyleDeclaration.setProperty()`.
*
* Property names are expected to be kebab-case, such as `font-size`, and
* values are expected to include explicit units where needed, such as `12px`.
*/
export const getStyleObjectFromCSS = getStyleObjectFromCSS_;
/**
* Applies inline CSS text to a DOM style declaration using
* `CSSStyleDeclaration.setProperty()`.
*
* Property names are expected to be kebab-case, such as `font-size`, and
* values are expected to include explicit units where needed, such as `12px`.
*/
export const setDOMStyleFromCSS = setDOMStyleFromCSS_;
/**
* Applies a style object to a DOM style declaration using
* `CSSStyleDeclaration.setProperty()`.
*
* Property names are expected to be kebab-case, such as `font-size`, and
* values are expected to include explicit units where needed, such as `12px`.
*/
export const setDOMStyleObject = setDOMStyleObject_;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of this should be colocated with the definitions of these functions

@etrepum etrepum added this pull request to the merge queue Apr 22, 2026
Merged via the queue into facebook:main with commit 1ca42f1 Apr 22, 2026
70 of 71 checks passed
vishisht31 pushed a commit that referenced this pull request Apr 23, 2026
…] Refactor: make runtime style updates CSP-safe (#8372)
levensta added a commit to levensta/lexical that referenced this pull request Apr 24, 2026
commit 5d1bc33
Author: Sathvik Veerapaneni <98241593+Sathvik-Chowdary-Veerapaneni@users.noreply.github.com>
Date:   Thu Apr 23 13:12:21 2026 -0400

    [lexical-list] Bug Fix: Merge nested list into parent <li> during HTML export (facebook#8313)

    Co-authored-by: Bob Ippolito <bob@redivi.com>

commit 2c37dc2
Author: Bob Ippolito <bob@redivi.com>
Date:   Thu Apr 23 07:14:14 2026 -0700

    [lexical-clipboard][lexical-rich-text][lexical-plain-text] Bug Fix: Drag-and-drop within the same block (facebook#8373)

    Co-authored-by: Claude <noreply@anthropic.com>

commit ca2aa31
Author: Bob Ippolito <bob@redivi.com>
Date:   Thu Apr 23 07:12:43 2026 -0700

    [lexical][lexical-utils][lexical-list] Bug Fix: Clean up and test $insertNodeToNearestRootAtCaret edge cases (facebook#8384)

commit 207648e
Author: Bob Ippolito <bob@redivi.com>
Date:   Thu Apr 23 07:11:52 2026 -0700

    [lexical-html][lexical-playground] Feature: Implement a well-defined ordering for DOMRenderExtension overrides and add $decorateDOM (facebook#8368)

commit 1ca42f1
Author: Agyei Holy <agyeiholy978@gmail.com>
Date:   Wed Apr 22 15:39:37 2026 -0500

    [lexical][lexical-code-core][lexical-list][lexical-table][lexical-yjs] Refactor: make runtime style updates CSP-safe (facebook#8372)

commit ca0ce82
Author: Bob Ippolito <bob@redivi.com>
Date:   Wed Apr 22 12:57:28 2026 -0700

    [lexical-list] Bug Fix: Ensure that ListItemNode always has a ListItem parent (facebook#8382)

commit f4c44e1
Author: Sherry <potatowagon@meta.com>
Date:   Thu Apr 23 00:28:54 2026 +0530

    [lexical-markdown] Bug Fix: Code spans take precedence over inline formatting in shortcuts (facebook#8381)

commit 4a43cb0
Author: Sergey Gorbachev <grbchv.s@gmail.com>
Date:   Wed Apr 22 18:31:21 2026 +0300

    [lexical-playground] Feature: HTML conversion button (facebook#8379)
levensta added a commit to levensta/lexical that referenced this pull request Apr 24, 2026
commit 5d1bc33
Author: Sathvik Veerapaneni <98241593+Sathvik-Chowdary-Veerapaneni@users.noreply.github.com>
Date:   Thu Apr 23 13:12:21 2026 -0400

    [lexical-list] Bug Fix: Merge nested list into parent <li> during HTML export (facebook#8313)

    Co-authored-by: Bob Ippolito <bob@redivi.com>

commit 2c37dc2
Author: Bob Ippolito <bob@redivi.com>
Date:   Thu Apr 23 07:14:14 2026 -0700

    [lexical-clipboard][lexical-rich-text][lexical-plain-text] Bug Fix: Drag-and-drop within the same block (facebook#8373)

    Co-authored-by: Claude <noreply@anthropic.com>

commit ca2aa31
Author: Bob Ippolito <bob@redivi.com>
Date:   Thu Apr 23 07:12:43 2026 -0700

    [lexical][lexical-utils][lexical-list] Bug Fix: Clean up and test $insertNodeToNearestRootAtCaret edge cases (facebook#8384)

commit 207648e
Author: Bob Ippolito <bob@redivi.com>
Date:   Thu Apr 23 07:11:52 2026 -0700

    [lexical-html][lexical-playground] Feature: Implement a well-defined ordering for DOMRenderExtension overrides and add $decorateDOM (facebook#8368)

commit 1ca42f1
Author: Agyei Holy <agyeiholy978@gmail.com>
Date:   Wed Apr 22 15:39:37 2026 -0500

    [lexical][lexical-code-core][lexical-list][lexical-table][lexical-yjs] Refactor: make runtime style updates CSP-safe (facebook#8372)

commit ca0ce82
Author: Bob Ippolito <bob@redivi.com>
Date:   Wed Apr 22 12:57:28 2026 -0700

    [lexical-list] Bug Fix: Ensure that ListItemNode always has a ListItem parent (facebook#8382)

commit f4c44e1
Author: Sherry <potatowagon@meta.com>
Date:   Thu Apr 23 00:28:54 2026 +0530

    [lexical-markdown] Bug Fix: Code spans take precedence over inline formatting in shortcuts (facebook#8381)

commit 4a43cb0
Author: Sergey Gorbachev <grbchv.s@gmail.com>
Date:   Wed Apr 22 18:31:21 2026 +0300

    [lexical-playground] Feature: HTML conversion button (facebook#8379)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. extended-tests Run extended e2e tests on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Refactor style management to be CSP-safe

3 participants