Skip to content

feat: add an opt-in MFE theme to the Text (HTML) XBlock [WIP] - #308

Draft
rpenido wants to merge 1 commit into
openedx:mainfrom
open-craft:rpenido/text-xblock-include-theme
Draft

rpenido wants to merge 1 commit into
openedx:mainfrom
open-craft:rpenido/text-xblock-include-theme

Conversation

@rpenido

@rpenido rpenido commented Oct 1, 2026 •

Copy link
Copy Markdown

Description

Adds an opt-in include_theme setting to the Text (HTML) XBlock. When it is on, the block renders its author-supplied HTML inside a shadow root and applies the deployment's Paragon theme to it, so page styles cannot reach the content and the content cannot leak styles back out into the page.

Blocks created before this change, and blocks where the author leaves the setting off, render exactly as they did: the legacy html.css is loaded for them and skipped only for themed blocks.

xblock-theme

Testing instructions

Setup

To check the feature, the deployment has to publish Paragon theme URLs the block can read, including one that cannot be missed. A single Tutor plugin does all of it and carries its own check stylesheet, so there is nothing else to create or mount. It also adds the Studio origin to the LMS CORS whitelist: Studio renders the block in an iframe on its own origin, from where the config API is not reachable unless that origin is allowed. Save this as xblock_theme_check.py in your TUTOR_PLUGINS_ROOT:

"""Publish the Paragon theme URLs the Text block's include_theme reads.
"""
import urllib.parse
from tutor import hooks

# The CSS "file", as a URL
_CHECK_URL = "data:text/css;charset=utf-8," + urllib.parse.quote(
    "h1, h2 { color: magenta !important; }"
)

_LMS_PATCH = """
# Studio renders the block in an iframe on the Studio origin, from where the
# request to the LMS config API is cross-origin, and that API is not CORS-enabled
# by default.
_studio_origin = MFE_CONFIG.get("STUDIO_BASE_URL")
if _studio_origin and _studio_origin not in CORS_ORIGIN_WHITELIST:
    CORS_ORIGIN_WHITELIST.append(_studio_origin)

_paragon_urls = MFE_CONFIG.setdefault("PARAGON_THEME_URLS", {})
_paragon_urls.setdefault("core", {"url": "https://cdn.jsdelivr.net/npm/@openedx/paragon@23/dist/core.min.css"})
_paragon_urls.setdefault("variants", {}).setdefault("light", {}).setdefault("urls", {})["brandOverride"] = __URL__
"""

_LMS_PATCH = _LMS_PATCH.replace("__URL__", repr(_CHECK_URL))

hooks.Filters.ENV_PATCHES.add_item(("openedx-lms-development-settings", _LMS_PATCH))

Enable it and rebuild:

tutor plugins enable xblock_theme_check
tutor config save
tutor dev launch -I

You will also need to have openedx/openedx-platform#39178 and openedx/frontend-app-authoring#3271 bind-mounted in your stack.

  1. In Studio, open a course and add a Text (HTML) block. The Use MFE Theme toggle starts off - the setting is opt-in, so check it to turn it on for this block.
  2. Type some content and save. Open the block again: the toggle must come back on, not reset.
  3. With the toggle on, the editing area itself picks up the theme - a heading or link colour declared in the brand override must change inside the editor, not only in the learner view.
  4. With the toggle on and the content untouched, turn Use MFE Theme off and leave it off. The block must register as unsaved even though the content never changed: Cancel must raise the "Are you sure you want to exit the editor?" confirmation, and reloading the page must show the browser's leave-page prompt. Turning it back on must then clear the unsaved state - a round trip that ends where it started is not a change, so only the first half of that is expected to be dirty.
  5. Leave the toggle on and save.
  6. Open the block in the learner view. The content must render inside a shadow root. Verify in the console that document.querySelector('.xblock_html').getRootNode().constructor.name returns "ShadowRoot". Note that document.querySelector('.xblock-root') returns null by design - that container is created inside the shadow root and document.querySelector does not cross the boundary; reach it with document.querySelector('.xblock_html').shadowRoot.querySelector('.xblock-root').
  7. The theme must apply. A heading colour declared in the brand override stylesheet should now be visible.
  8. Page CSS must not reach in. Set a distinctive body { font-family: ... } and confirm the block does not inherit it. The author's own CSS stays inside the block for the same reason. Expect one deliberate exception: the theme stylesheets are appended to document.head as well as to the shadow root, because Paragon declares its custom properties on :root and :root does not cross a shadow boundary - so seeing the theme on the rest of the page is intended, not a leak.
  9. Regression check. Open an unrelated pre-existing HTML block in the same course, with the toggle off. It must render with its usual styling and no shadow root.
  10. Round-trip. Export the course and inspect the block's OLX. With the setting on, the <html> element carries include_theme="true"; with it off, the attribute is absent - the serializer only writes the field when it is set, so the default never appears in OLX. This depends on fix: serialize the Text (HTML) XBlock's include_theme setting [WIP] openedx-platform#39178 - the serializer uses a hardcoded allowlist and silently drops the field without it.
  11. Library authoring. Add a Text block to a library and repeat steps 1-5. The toggle, the unsaved-change warning and the save behave the same in a library as in a course.

Other information

Merge checklist:
Check off if complete or not applicable:

  • Version bumped
  • Changelog record added
  • Documentation updated (not only docstrings)
  • Fixup commits are squashed away
  • Unit tests added/updated
  • Manual testing instructions provided
  • Noted any: Concerns, dependencies, migration issues, deadlines, tickets

Private ref: FAL-4394

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Oct 1, 2026
@openedx-webhooks

Copy link
Copy Markdown

Thanks for the pull request, @rpenido!

This repository is currently maintained by @openedx/axim-engineering.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

🔘 Update the status of your PR

Your PR is currently marked as a draft. After completing the steps above, update its status by clicking "Ready for Review", or removing "WIP" from the title, as appropriate.


Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@rpenido
rpenido force-pushed the rpenido/text-xblock-include-theme branch from ec088e7 to 25702c6 Compare October 2, 2026 07:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

open-source-contribution PR author is not from Axim or 2U

Projects

Status: Needs Triage

Development

Successfully merging this pull request may close these issues.

2 participants