jenkins-bot has submitted this change and it was merged. Change subject: ve.utils: Fix bug in batchSplice polyfill ......................................................................
ve.utils: Fix bug in batchSplice polyfill In order to test the polyfill, we have to check the support on each call so it can be overridden. Cache the support check so this isn't an expensive overhead. Bonus: Fix return value of polyfill ('removed') by adding the offset to the second argument. Change-Id: I15f9e2fa88fc53d8af40bd76228e77a53cdf7672 --- M src/ve.utils.js M tests/ve.test.js 2 files changed, 116 insertions(+), 89 deletions(-) Approvals: Krinkle: Looks good to me, approved jenkins-bot: Verified diff --git a/src/ve.utils.js b/src/ve.utils.js index fe1380a..cf63d0e 100644 --- a/src/ve.utils.js +++ b/src/ve.utils.js @@ -174,6 +174,34 @@ ve.extendObject = $.extend; /** + * @private + * @property {boolean} + */ +ve.supportSplice = ( function () { + var a, n; + + // This returns false in Safari 8 + a = new Array( 100000 ); + a.splice( 30, 0, 'x' ); + a.splice( 20, 1 ); + if ( a.indexOf( 'x' ) !== 29 ) { + return false; + } + + // This returns false in Opera 12.15 + a = []; + n = 256; + a[n] = 'a'; + a.splice( n + 1, 0, 'b' ); + if ( a[n] !== 'a' ) { + return false; + } + + // Splice is supported + return true; +} )(); + +/** * Splice one array into another. * * This is the equivalent of arr.splice( offset, remove, d1, d2, d3, ... ) except that arguments are @@ -183,7 +211,7 @@ * performance tests should be conducted on each use of this method to verify this is true for the * particular use. Also, browsers change fast, never assume anything, always test everything. * - * Includes a replacement for broken implementation of Array.prototype.splice() found in Opera 12. + * Includes a replacement for broken implementations of Array.prototype.splice(). * * @param {Array|ve.dm.BranchNode} arr Target object (must have `splice` method, object will be modified) * @param {number} offset Offset in arr to splice at. This may NOT be negative, unlike the @@ -192,80 +220,61 @@ * @param {Array} data Array of items to insert at the offset. Must be non-empty if remove=0 * @return {Array} Array of items removed */ -ve.batchSplice = ( function () { - var arraySplice; +ve.batchSplice = function ( arr, offset, remove, data ) { + // We need to splice insertion in in batches, because of parameter list length limits which vary + // cross-browser - 1024 seems to be a safe batch size on all browsers + var splice, spliced, + index = 0, + batchSize = 1024, + toRemove = remove, + removed = []; - // This returns true in Opera 12.15 - function spliceBrokenOpera() { - var n = 256, - a = []; - a[n] = 'a'; - a.splice( n + 1, 0, 'b' ); - return a[n] !== 'a'; - } - - // This returns true in Safari 8 - function spliceBrokenSafari() { - var a = new Array( 100000 ); - a.splice( 30, 0, 'x' ); - a.splice( 20, 1 ); - return a.indexOf( 'x' ) !== 29; - } - - if ( !spliceBrokenOpera() && !spliceBrokenSafari() ) { - arraySplice = Array.prototype.splice; + if ( !Array.isArray( arr ) ) { + splice = arr.splice; } else { - // Standard Array.prototype.splice() function implemented using .slice() and .push(). - arraySplice = function ( offset, remove/*, data... */ ) { - var data, begin, removed, end; + if ( ve.supportSplice ) { + splice = Array.prototype.splice; + } else { + // Standard Array.prototype.splice() function implemented using .slice() and .push(). + splice = function ( offset, remove/*, data... */ ) { + var data, begin, removed, end; - data = Array.prototype.slice.call( arguments, 2 ); + data = Array.prototype.slice.call( arguments, 2 ); - begin = this.slice( 0, offset ); - removed = this.slice( offset, remove ); - end = this.slice( offset + remove ); + begin = this.slice( 0, offset ); + removed = this.slice( offset, offset + remove ); + end = this.slice( offset + remove ); - this.length = 0; - ve.batchPush( this, begin ); - ve.batchPush( this, data ); - ve.batchPush( this, end ); + this.length = 0; + ve.batchPush( this, begin ); + ve.batchPush( this, data ); + ve.batchPush( this, end ); - return removed; - }; + return removed; + }; + } } - return function ( arr, offset, remove, data ) { - // We need to splice insertion in in batches, because of parameter list length limits which vary - // cross-browser - 1024 seems to be a safe batch size on all browsers - var splice, spliced, - index = 0, - batchSize = 1024, - toRemove = remove, - removed = []; + if ( data.length === 0 ) { + // Special case: data is empty, so we're just doing a removal + // The code below won't handle that properly, so we do it here + return splice.call( arr, offset, remove ); + } - splice = Array.isArray( arr ) ? arraySplice : arr.splice; - - if ( data.length === 0 ) { - // Special case: data is empty, so we're just doing a removal - // The code below won't handle that properly, so we do it here - return splice.call( arr, offset, remove ); + while ( index < data.length ) { + // Call arr.splice( offset, remove, i0, i1, i2, ..., i1023 ); + // Only set remove on the first call, and set it to zero on subsequent calls + spliced = splice.apply( + arr, [index + offset, toRemove].concat( data.slice( index, index + batchSize ) ) + ); + if ( toRemove > 0 ) { + removed = spliced; } - - while ( index < data.length ) { - // Call arr.splice( offset, remove, i0, i1, i2, ..., i1023 ); - // Only set remove on the first call, and set it to zero on subsequent calls - spliced = splice.apply( - arr, [index + offset, toRemove].concat( data.slice( index, index + batchSize ) ) - ); - if ( toRemove > 0 ) { - removed = spliced; - } - index += batchSize; - toRemove = 0; - } - return removed; - }; -}() ); + index += batchSize; + toRemove = 0; + } + return removed; +}; /** * Insert one array into another. diff --git a/tests/ve.test.js b/tests/ve.test.js index c125515..28529f7 100644 --- a/tests/ve.test.js +++ b/tests/ve.test.js @@ -266,35 +266,53 @@ ); } ); -QUnit.test( 'batchSplice', 8, function ( assert ) { - var actualRet, expectedRet, i, - actual = [ 'a', 'b', 'c', 'd', 'e' ], - expected = actual.slice( 0 ), - bigArr = []; +QUnit.test( 'batchSplice', function ( assert ) { + var spliceWasSupported = ve.supportSplice; - actualRet = ve.batchSplice( actual, 1, 1, [] ); - expectedRet = expected.splice( 1, 1 ); - assert.deepEqual( expectedRet, actualRet, 'removing 1 element (return value)' ); - assert.deepEqual( expected, actual, 'removing 1 element (array)' ); + function assertBatchSplice() { + var actualRet, expectedRet, msg, i, + actual = [ 'a', 'b', 'c', 'd', 'e' ], + expected = actual.slice( 0 ), + bigArr = []; - actualRet = ve.batchSplice( actual, 3, 2, [ 'w', 'x', 'y', 'z' ] ); - expectedRet = expected.splice( 3, 2, 'w', 'x', 'y', 'z' ); - assert.deepEqual( expectedRet, actualRet, 'replacing 2 elements with 4 elements (return value)' ); - assert.deepEqual( expected, actual, 'replacing 2 elements with 4 elements (array)' ); + msg = ve.supportSplice ? 'Array#splice native' : 'Array#splice polyfill'; - actualRet = ve.batchSplice( actual, 0, 0, [ 'f', 'o', 'o' ] ); - expectedRet = expected.splice( 0, 0, 'f', 'o', 'o' ); - assert.deepEqual( expectedRet, actualRet, 'inserting 3 elements (return value)' ); - assert.deepEqual( expected, actual, 'inserting 3 elements (array)' ); + actualRet = ve.batchSplice( actual, 1, 1, [] ); + expectedRet = expected.splice( 1, 1 ); + assert.deepEqual( expectedRet, actualRet, msg + ': removing 1 element (return value)' ); + assert.deepEqual( expected, actual, msg + ': removing 1 element (array)' ); - for ( i = 0; i < 2100; i++ ) { - bigArr[i] = i; + actualRet = ve.batchSplice( actual, 3, 2, [ 'w', 'x', 'y', 'z' ] ); + expectedRet = expected.splice( 3, 2, 'w', 'x', 'y', 'z' ); + assert.deepEqual( expectedRet, actualRet, msg + ': replacing 2 elements with 4 elements (return value)' ); + assert.deepEqual( expected, actual, msg + ': replacing 2 elements with 4 elements (array)' ); + + actualRet = ve.batchSplice( actual, 0, 0, [ 'f', 'o', 'o' ] ); + expectedRet = expected.splice( 0, 0, 'f', 'o', 'o' ); + assert.deepEqual( expectedRet, actualRet, msg + ': inserting 3 elements (return value)' ); + assert.deepEqual( expected, actual, msg + ': inserting 3 elements (array)' ); + + for ( i = 0; i < 2100; i++ ) { + bigArr[i] = i; + } + actualRet = ve.batchSplice( actual, 2, 3, bigArr ); + expectedRet = expected.splice.apply( expected, [2, 3].concat( bigArr.slice( 0, 1050 ) ) ); + expected.splice.apply( expected, [1052, 0].concat( bigArr.slice( 1050 ) ) ); + assert.deepEqual( expectedRet, actualRet, msg + ': replacing 3 elements with 2100 elements (return value)' ); + assert.deepEqual( expected, actual, msg + ': replacing 3 elements with 2100 elements (array)' ); } - actualRet = ve.batchSplice( actual, 2, 3, bigArr ); - expectedRet = expected.splice.apply( expected, [2, 3].concat( bigArr.slice( 0, 1050 ) ) ); - expected.splice.apply( expected, [1052, 0].concat( bigArr.slice( 1050 ) ) ); - assert.deepEqual( expectedRet, actualRet, 'replacing 3 elements with 2100 elements (return value)' ); - assert.deepEqual( expected, actual, 'replacing 3 elements with 2100 elements (array)' ); + + QUnit.expect( 8 * ( spliceWasSupported ? 2 : 1 ) ); + + assertBatchSplice(); + + // If the current browser supported native splice, + // test again without the native splice. + if ( spliceWasSupported ) { + ve.supportSplice = false; + assertBatchSplice(); + ve.supportSplice = true; + } } ); QUnit.test( 'insertIntoArray', 3, function ( assert ) { -- To view, visit https://gerrit.wikimedia.org/r/203054 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I15f9e2fa88fc53d8af40bd76228e77a53cdf7672 Gerrit-PatchSet: 4 Gerrit-Project: VisualEditor/VisualEditor Gerrit-Branch: master Gerrit-Owner: Esanders <esand...@wikimedia.org> Gerrit-Reviewer: Catrope <roan.katt...@gmail.com> Gerrit-Reviewer: Krinkle <krinklem...@gmail.com> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits