Skip to content

fix: prevent XBlock container crash when JS resources are blocked by browser extensions#46

Open
bra-i-am wants to merge 1 commit into
nau/teak.masterfrom
bc/fix-drag-and-drop-with-adblocker-teak
Open

fix: prevent XBlock container crash when JS resources are blocked by browser extensions#46
bra-i-am wants to merge 1 commit into
nau/teak.masterfrom
bc/fix-drag-and-drop-with-adblocker-teak

Conversation

@bra-i-am

@bra-i-am bra-i-am commented Jun 25, 2026

Copy link
Copy Markdown

Note

This fix is intentionally scoped to NAU's fork targeting the teak release. It is not being submitted upstream because the issue no longer reproduces on master (Verawood), meaning the upstream codebase is already clean of this bug — likely resolved as a side effect of broader refactors between releases.

Fixes https://github.com/fccn/nau-technical/issues/838

Fixes a resilience bug in Studio's XBlock container page where Chrome extensions (e.g. AdBlock) that block certain JS resources cause an infinite loading spinner and crash the entire unit — preventing all XBlocks from rendering, not just the one whose resource was blocked.

Root cause analysis:

When any JS resource fails to load, addXBlockFragmentResources called deferred.reject() — and since renderXBlockFragment only hooks .done(), the XBlock HTML was never injected into the page. The server always sends the spinner as initial HTML; JavaScript is responsible for replacing it. With JS blocked, the spinner stayed forever.

For the Drag & Drop v2 XBlock specifically, AdBlock filter lists target these two scripts by filename pattern:

  • drag_and_drop_v2/public/js/vendor/virtual-dom-*.js
  • drag_and_drop_v2/public/js/drag_and_drop.*.js

Both are served from the same domain (no CDN), so there is no cross-origin workaround available.

Four root causes fixed:

  1. loadResource used ViewUtils.loadJavaScript (backed by $script/scriptjs), which invokes its callback on both onload and onerror. When AdBlock blocks a script, the promise always resolved — making failures invisible to the application. Fixed by replacing it with a raw <script> element that wires onerror → reject directly.

  2. addXBlockFragmentResources rejected the deferred on first JS failure, which prevented renderXBlockFragment's .done() handler from running — so the XBlock HTML was never injected and the spinner persisted. Fixed by resolving the deferred with a {failedJs: true} flag instead of rejecting, so the HTML is always injected regardless of whether the JS loaded. A window.failedXBlockResources cache (mirroring the existing window.loadedXBlockResources pattern) prevents redundant network requests for known-blocked resources on subsequent XBlock renders within the same page session.

  3. constructBlock in core.js crashed with TypeError when an XBlock's initFn was undefined (because its JS never loaded). The crash propagated to the outer try/catch in handleXBlockFragment, which called errorCallback() instead of successCallback() — leaving the container stuck. Fixed with a null check: if initFn is missing, the affected XBlock is marked with xblock-initialization-failed and a stub is returned, allowing the rest of the vertical to initialize normally. Additionally, fragmentsRendered.always() was changed to .done() so that XBlock initialization is only attempted when the fragment rendered successfully.

  4. container.js message handler logged a spurious warning loop for every postMessage event with type: undefined sent by browser extension proxies (e.g. Redux DevTools) via setInterval. Fixed by only logging for defined, unrecognized message types.

Behavior after this fix:

  • XBlocks whose JS resources are blocked render their HTML and receive the xblock-initialization-failed CSS class. Their JS is not initialized, so interactive behavior is unavailable — but the block is visible and does not crash the page.
  • All other XBlocks in the same unit are completely unaffected.
  • A console.warn is emitted for each blocked resource and each skipped init function, replacing the previous silent freeze.

Before: Chrome + AdBlock → infinite spinner, TypeError: Cannot read properties of undefined (reading 'prototype'), all XBlocks in the unit broken.

After: Chrome + AdBlock → affected XBlock shows its server-rendered HTML with xblock-initialization-failed, all other XBlocks render and initialize normally, no JS crash.

Testing instructions

Prerequisites: A Studio unit with a Drag & Drop v2 XBlock alongside at least one other XBlock (e.g. Problem/Single Select).

  1. We'll test this in dev environment using nau-tutor-configs: run the environment with the command bin/tutor dev start

  2. Open Studio and navigate to a unit with a Drag & Drop v2 XBlock and some more XBlocks

  3. Block the URLs manually through the dev tools of the browser, and you'll be able to replicate the bug like this

    image
  4. Change the edx-platform branch to this PR, enter the container by running bin/tutor dev exec cms bash and compile the assets running npm run webpack-dev in the container

  5. Fixed!

    image

@bra-i-am bra-i-am marked this pull request as ready for review June 25, 2026 15:51
@igobranco igobranco requested a review from ManuelStarDo June 25, 2026 16:09
@bra-i-am bra-i-am requested a review from efortish June 25, 2026 16:31

@efortish efortish left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

I like the approach and I agree as well with your idea about we should only fix it here in our fork because it is not happening in the new xblock versions on upstream.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants