From 4394d6cf8a7913e90df119e32f67dd51b8f2ddcb Mon Sep 17 00:00:00 2001 From: Fomafix Date: Mon, 8 Dec 2014 20:18:33 +0000 Subject: [PATCH] mw.loader: Guard against Object.prototype keys as module names This avoids conflicts where we confuse an Object method for a registered module. Change-Id: I1b1c2db355f0c698be4a5fe797daa55dedc25258 --- resources/src/mediawiki/mediawiki.js | 36 +++++++++---------- .../resources/mediawiki/mediawiki.test.js | 24 +++++++++++++ 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/resources/src/mediawiki/mediawiki.js b/resources/src/mediawiki/mediawiki.js index bb916639b6..c8f5506833 100644 --- a/resources/src/mediawiki/mediawiki.js +++ b/resources/src/mediawiki/mediawiki.js @@ -840,7 +840,7 @@ function sortDependencies( module, resolved, unresolved ) { var n, deps, len, skip; - if ( registry[module] === undefined ) { + if ( !hasOwn.call( registry, module ) ) { throw new Error( 'Unknown dependency: ' + module ); } @@ -954,7 +954,7 @@ // Build a list of modules which are in one of the specified states for ( s = 0; s < states.length; s += 1 ) { for ( m = 0; m < modules.length; m += 1 ) { - if ( registry[modules[m]] === undefined ) { + if ( !hasOwn.call( registry, modules[m] ) ) { // Module does not exist if ( states[s] === 'unregistered' ) { // OK, undefined @@ -1098,7 +1098,7 @@ var key, value, media, i, urls, cssHandle, checkCssHandles, cssHandlesRegistered = false; - if ( registry[module] === undefined ) { + if ( !hasOwn.call( registry, module ) ) { throw new Error( 'Module has not been registered yet: ' + module ); } else if ( registry[module].state === 'registered' ) { throw new Error( 'Module has not been requested from the server yet: ' + module ); @@ -1434,7 +1434,7 @@ // Appends a list of modules from the queue to the batch for ( q = 0; q < queue.length; q += 1 ) { // Only request modules which are registered - if ( registry[queue[q]] !== undefined && registry[queue[q]].state === 'registered' ) { + if ( hasOwn.call( registry, queue[q] ) && registry[queue[q]].state === 'registered' ) { // Prevent duplicate entries if ( $.inArray( queue[q], batch ) === -1 ) { batch[batch.length] = queue[q]; @@ -1497,10 +1497,10 @@ for ( b = 0; b < batch.length; b += 1 ) { bSource = registry[batch[b]].source; bGroup = registry[batch[b]].group; - if ( splits[bSource] === undefined ) { + if ( !hasOwn.call( splits, bSource ) ) { splits[bSource] = {}; } - if ( splits[bSource][bGroup] === undefined ) { + if ( !hasOwn.call( splits[bSource], bGroup ) ) { splits[bSource][bGroup] = []; } bSourceGroup = splits[bSource][bGroup]; @@ -1553,7 +1553,7 @@ prefix = modules[i].substr( 0, lastDotIndex ); suffix = modules[i].slice( lastDotIndex + 1 ); - bytesAdded = moduleMap[prefix] !== undefined + bytesAdded = hasOwn.call( moduleMap, prefix ) ? suffix.length + 3 // '%2C'.length == 3 : modules[i].length + 3; // '%7C'.length == 3 @@ -1567,7 +1567,7 @@ async = true; l = currReqBaseLength + 9; } - if ( moduleMap[prefix] === undefined ) { + if ( !hasOwn.call( moduleMap, prefix ) ) { moduleMap[prefix] = []; } moduleMap[prefix].push( suffix ); @@ -1609,7 +1609,7 @@ return true; } - if ( sources[id] !== undefined ) { + if ( hasOwn.call( sources, id ) ) { throw new Error( 'source already registered: ' + id ); } @@ -1659,7 +1659,7 @@ if ( typeof module !== 'string' ) { throw new Error( 'module must be a string, not a ' + typeof module ); } - if ( registry[module] !== undefined ) { + if ( hasOwn.call( registry, module ) ) { throw new Error( 'module already registered: ' + module ); } // List the module as registered @@ -1726,11 +1726,11 @@ throw new Error( 'templates must be an object, not a ' + typeof templates ); } // Automatically register module - if ( registry[module] === undefined ) { + if ( !hasOwn.call( registry, module ) ) { mw.loader.register( module ); } // Check for duplicate implementation - if ( registry[module] !== undefined && registry[module].script !== undefined ) { + if ( hasOwn.call( registry, module ) && registry[module].script !== undefined ) { throw new Error( 'module already implemented: ' + module ); } // Attach components @@ -1855,8 +1855,8 @@ // an array of unrelated modules, whereas the modules passed to // using() are related and must all be loaded. for ( filtered = [], m = 0; m < modules.length; m += 1 ) { - module = registry[modules[m]]; - if ( module !== undefined ) { + if ( hasOwn.call( registry, modules[m] ) ) { + module = registry[modules[m]]; if ( $.inArray( module.state, ['error', 'missing'] ) === -1 ) { filtered[filtered.length] = modules[m]; } @@ -1895,7 +1895,7 @@ } return; } - if ( registry[module] === undefined ) { + if ( !hasOwn.call( registry, module ) ) { mw.loader.register( module ); } if ( $.inArray( state, ['ready', 'error', 'missing'] ) !== -1 @@ -1917,7 +1917,7 @@ * in the registry. */ getVersion: function ( module ) { - if ( !registry[module] || registry[module].version === undefined ) { + if ( !hasOwn.call( registry, module ) || registry[module].version === undefined ) { return null; } return formatVersionNumber( registry[module].version ); @@ -1931,7 +1931,7 @@ * in the registry. */ getState: function ( module ) { - if ( !registry[module] || registry[module].state === undefined ) { + if ( !hasOwn.call( registry, module ) || registry[module].state === undefined ) { return null; } return registry[module].state; @@ -2017,7 +2017,7 @@ * @return {string|null} Module key or null if module does not exist */ getModuleKey: function ( module ) { - return typeof registry[module] === 'object' ? + return hasOwn.call( registry, module ) ? ( module + '@' + registry[module].version ) : null; }, diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js index 7e0ee917f3..409f3e692e 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js @@ -396,6 +396,30 @@ } ); } ); + QUnit.asyncTest( 'mw.loader with Object method as module name', 2, function ( assert ) { + var isAwesomeDone; + + mw.loader.testCallback = function () { + QUnit.start(); + assert.strictEqual( isAwesomeDone, undefined, 'Implementing module hasOwnProperty: isAwesomeDone should still be undefined' ); + isAwesomeDone = true; + }; + + mw.loader.implement( 'hasOwnProperty', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' )], {}, {} ); + + mw.loader.using( 'hasOwnProperty', function () { + + // /sample/awesome.js declares the "mw.loader.testCallback" function + // which contains a call to start() and ok() + assert.strictEqual( isAwesomeDone, true, 'hasOwnProperty module should\'ve caused isAwesomeDone to be true' ); + delete mw.loader.testCallback; + + }, function () { + QUnit.start(); + assert.ok( false, 'Error callback fired while loader.using "hasOwnProperty" module' ); + } ); + } ); + QUnit.asyncTest( 'mw.loader.using( .. ).promise', 2, function ( assert ) { var isAwesomeDone; -- 2.20.1