Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cms/static/js/views/pages/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,9 @@ function($, _, Backbone, gettext, BasePage,
xblockWrapper.addClass('is-selected');
break;
default:
console.warn('Unhandled message type:', data.type);
if (data.type) {
console.warn('Unhandled message type:', data.type);
}
}
});
}
Expand Down
55 changes: 50 additions & 5 deletions cms/static/js/views/xblock.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function($, _, ViewUtils, BaseView, XBlock, HtmlUtils) {
aside;

fragmentsRendered = this.renderXBlockFragment(fragment, wrapper);
fragmentsRendered.always(function() {
fragmentsRendered.done(function() {
xblockElement = self.$('.xblock').first();
try {
xblock = XBlock.initializeBlock(xblockElement);
Expand Down Expand Up @@ -180,29 +180,61 @@ function($, _, ViewUtils, BaseView, XBlock, HtmlUtils) {
var self = this,
applyResource,
numResources,
deferred;
deferred,
failedJs = false;
numResources = resources.length;
deferred = $.Deferred();
applyResource = function(index) {
var hash, resource, value, promise;
if (index >= numResources) {
deferred.resolve();
deferred.resolve({
failedJs: failedJs
});
return;
}
value = resources[index];
hash = value[0];
if (!window.loadedXBlockResources) {
window.loadedXBlockResources = [];
}
if (!window.failedXBlockResources) {
window.failedXBlockResources = [];
}
// A JS resource that previously failed (e.g., blocked by an extension):
// mark failedJs so the specific XBlock that needs it gets skipped,
// but continue loading the rest of the fragment's resources so that
// other XBlocks in the same fragment are not affected.
if (_.indexOf(window.failedXBlockResources, hash) >= 0) {
failedJs = true;
applyResource(index + 1);
return;
}

if (_.indexOf(window.loadedXBlockResources, hash) < 0) {
resource = value[1];
promise = self.loadResource(resource);
window.loadedXBlockResources.push(hash);
promise.done(function() {
applyResource(index + 1);
}).fail(function() {
deferred.reject();
console.warn(
'Failed to load XBlock resource:',
resource.data || resource.kind
);

if (resource.mimetype === 'application/javascript') {
failedJs = true;

// Remember this hash so subsequent fragments that reference
// the same resource skip it without a network round-trip.
window.failedXBlockResources.push(hash);
}

// Always continue — other resources in this fragment may belong
// to unrelated XBlocks and must still be loaded.
applyResource(index + 1);
});

} else {
applyResource(index + 1);
}
Expand Down Expand Up @@ -235,7 +267,20 @@ function($, _, ViewUtils, BaseView, XBlock, HtmlUtils) {
// xss-lint: disable=javascript-jquery-append,javascript-concat-html
$head.append('<script>' + data + '</script>');
} else if (kind === 'url') {
return ViewUtils.loadJavaScript(data);
// Use a raw <script> tag instead of ViewUtils.loadJavaScript because
// the underlying $script (scriptjs) library invokes its callback on
// both onload AND onerror, making it impossible to detect blocked
// resources (e.g. from browser extensions). A raw tag fires onerror
// only on genuine network failure, letting addXBlockFragmentResources
// track failedJs correctly.
var scriptDeferred = $.Deferred();
var scriptEl = document.createElement('script');
scriptEl.type = 'text/javascript';
scriptEl.src = data;
scriptEl.onload = function() { scriptDeferred.resolve(); };
scriptEl.onerror = function() { scriptDeferred.reject(); };
$head[0].appendChild(scriptEl);
return scriptDeferred.promise();
}
} else if (mimetype === 'text/html') {
if (placement === 'head') {
Expand Down
15 changes: 15 additions & 0 deletions common/static/common/js/xblock/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@
block = (function() {
var initFn = window[$element.data('init')];

// initFn can be undefined when its JS was blocked (e.g. by an ad-blocker).
// Return null so the caller can fall back gracefully instead of crashing.
if (!initFn) {
console.warn('XBlock init function not found:', $element.data('init'));
return null;
}

// This create a new constructor that can then apply() the block_args
// to the initFn.
function Block() {
Expand All @@ -70,6 +77,14 @@

return new Block();
}());

if (!block) {
$element.trigger('xblock-initialized');
$element.data('initialized', true);
$element.addClass('xblock-initialized xblock-initialization-failed');
return {element: element, name: $element.data('name'), type: $element.data('block-type')};
}

block.runtime = runtime;
} else {
block = {};
Expand Down
Loading