diff --git a/resources/mmv/provider/mmv.provider.UserInfo.js b/resources/mmv/provider/mmv.provider.UserInfo.js index b64d7b429..96fe0501e 100644 --- a/resources/mmv/provider/mmv.provider.UserInfo.js +++ b/resources/mmv/provider/mmv.provider.UserInfo.js @@ -68,6 +68,8 @@ if ( repoInfo.apiUrl ) { ajaxOptions.url = repoInfo.apiUrl; ajaxOptions.dataType = 'jsonp'; + ajaxOptions.cache = true; // do not append `_=` to the URL + ajaxOptions.jsonpCallback = this.getCallbackName( username ); cacheKey = cacheKey + '|' + repoInfo.apiUrl; // local and remote user names could conflict } @@ -90,5 +92,23 @@ } ); }; + /**, + * Generate JSONP callback function name. + * jQuery uses a random string by default, which would break caching. + * On the other hand the callback needs to be unique to avoid surprises when multiple + * requests run in parallel. And of course needs to be a valid JS variable name. + * @param username + */ + UserInfo.prototype.getCallbackName = function ( username ) { + // Javascript variable name charset rules are fairly lax but better safe then sorry, + // so let's encode every non-alphanumeric character. + // Per http://stackoverflow.com/questions/1809153/maximum-length-of-variable-name-in-javascript + // length should not be an issue (might add a few hundred bytes to the request & response size + // for very long usernames, but we can live with that). + return 'mmv_userinfo_' + mw.util.rawurlencode( username )// encodes all characters except -.~_ + .replace( /-/g, '%2D' ).replace( /\./g, '%2E' ).replace( /~/g, '%7E' ).replace( /_/g, '%5F' ) + .replace( /%/g, '_' ); + }; + mw.mmv.provider.UserInfo = UserInfo; }( mediaWiki, jQuery, OO ) ); diff --git a/tests/qunit/mmv/provider/mmv.provider.UserInfo.test.js b/tests/qunit/mmv/provider/mmv.provider.UserInfo.test.js index 0933bb901..abcb4c36c 100644 --- a/tests/qunit/mmv/provider/mmv.provider.UserInfo.test.js +++ b/tests/qunit/mmv/provider/mmv.provider.UserInfo.test.js @@ -163,4 +163,26 @@ QUnit.start(); } ); } ); + + QUnit.test( 'getCallbackName()', 6, function ( assert ) { + var userInfoProvider = new mw.mmv.provider.UserInfo( {} ); + + function assertValidVariableName( username ) { + /*jshint evil:true */ + var varName = userInfoProvider.getCallbackName( username ); + try { + eval( 'var ' + varName + ';'); + assert.ok( true, 'Variable name ' + varName + ' generated from ' + username + ' is valid' ); + } catch ( e ) { + assert.ok( false, 'Variable name ' + varName + ' generated from ' + username + ' is invalid' ); + } + } + + assertValidVariableName( 'simple' ); + assertValidVariableName( 'Help! I have spaces!' ); + assertValidVariableName( 'oh_noes_i_has_underline' ); + assertValidVariableName( '$\'"+!%/=()[]{}:;<>,.?|' ); + assertValidVariableName( 'イチカワ エツヤ' ); + assertValidVariableName( new Array( 256 ).join( 'イ' ) ); // longest possible name, 255*2 bytes + } ); }( mediaWiki, jQuery ) );