Fix elem not selected if id contains special chars

If the id of a `.formulaequationinput input` element contains a special
character, then the selector for $preview was silently failing to match
the element, because no escaping was happening.

This fixes the issue by escaping the id before passing to the jQuery
selector function. CSS.escape is the ideal method, but this isn't
present in IE or Edge, so we use a fallback borrowed from the new
jQuery.escapeSelector method.
This commit is contained in:
Samuel Walladge
2020-02-07 14:56:29 +10:30
parent 063e1d45c8
commit 9c4b458d2a
2 changed files with 61 additions and 1 deletions

View File

@@ -1,3 +1,29 @@
describe('escapeSelector', function() {
'use strict';
var escapeSelector = window.escapeSelector;
it('correctly escapes css', function() {
// tests borrowed from https://github.com/jquery/jquery/blob/3edfa1bcdc50bca41ac58b2642b12f3feee03a3b/test/unit/selector.js#L2030
expect(escapeSelector('-')).toEqual('\\-');
expect(escapeSelector('-a')).toEqual('-a');
expect(escapeSelector('--')).toEqual('--');
expect(escapeSelector('--a')).toEqual('--a');
expect(escapeSelector('\uFFFD')).toEqual('\uFFFD');
expect(escapeSelector('\uFFFDb')).toEqual('\uFFFDb');
expect(escapeSelector('a\uFFFDb')).toEqual('a\uFFFDb');
expect(escapeSelector('1a')).toEqual('\\31 a');
expect(escapeSelector('a\0b')).toEqual('a\uFFFDb');
expect(escapeSelector('a3b')).toEqual('a3b');
expect(escapeSelector('-4a')).toEqual('-\\34 a');
expect(escapeSelector('\x01\x02\x1E\x1F')).toEqual('\\1 \\2 \\1e \\1f ');
// This is the important one; xblocks and course ids often contain invalid characters, so if these aren't
// escaped when embedding/searching xblock IDs using css selectors, bad things happen.
expect(escapeSelector('course-v1:edX+DemoX+Demo_Course')).toEqual('course-v1\\:edX\\+DemoX\\+Demo_Course');
expect(escapeSelector('block-v1:edX+DemoX+Demo_Course+type@sequential+block')).toEqual('block-v1\\:edX\\+DemoX\\+Demo_Course\\+type\\@sequential\\+block');
});
});
describe('Formula Equation Preview', function() {
'use strict';
var formulaEquationPreview = window.formulaEquationPreview;

View File

@@ -1,3 +1,37 @@
function escapeSelector(id) {
// Wrapper around window.CSS.escape that uses a fallback method if CSS.escape is not available.
// This is designed to serialize a string to be used as a valid css selector. See https://drafts.csswg.org/cssom/#the-css.escape()-method
// For example, can be used with xblock and course ids, which often contain invalid characters that must be escaped
// to function properly in css selectors.
// TODO: if this escaping is also required elsewhere, it may be useful to add a global CSS.escape polyfill and
// use that directly.
if (window.CSS && window.CSS.escape) {
return CSS.escape(id);
} else {
// CSS escape alternative borrowed from https://api.jquery.com/jQuery.escapeSelector/ source. When we upgrade to jQuery 3.0, we can use $.escapeSelector() instead of this shim escapeSelector function.
// source: https://github.com/jquery/jquery/blob/3edfa1bc/src/selector/escapeSelector.js
// CSS string/identifier serialization
// https://drafts.csswg.org/cssom/#common-serializing-idioms
var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;
function fcssescape( ch, asCodePoint ) {
if ( asCodePoint ) {
// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
if ( ch === "\0" ) {
return "\uFFFD";
}
// Control characters and (dependent upon position) numbers get escaped as code points
return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
}
// Other potentially-special ASCII characters get backslash-escaped
return "\\" + ch;
}
// ensure string and then run the replacements
return (id + "").replace(rcssescape, fcssescape);
}
}
var formulaEquationPreview = {
minDelay: 300, // Minimum time between requests sent out.
errorDelay: 1500 // Wait time before showing error (prevent frustration).
@@ -13,7 +47,7 @@ formulaEquationPreview.enable = function() {
function setupInput() {
var $this = $(this); // cache the jQuery object
var $preview = $('#' + this.id + '_preview');
var $preview = $('#' + escapeSelector(this.id) + '_preview');
var inputData = {
// These are the mutable values