diff --git a/src/lib/relink_private.js b/src/lib/relink_private.js index aaab64aca40..cade381bda5 100644 --- a/src/lib/relink_private.js +++ b/src/lib/relink_private.js @@ -20,13 +20,13 @@ var isPlainObject = require('./is_plain_object'); * This prevents deepCopying massive structures like a webgl context. */ module.exports = function relinkPrivateKeys(toContainer, fromContainer) { - var keys = Object.keys(fromContainer || {}); - - for(var i = 0; i < keys.length; i++) { - var k = keys[i], - fromVal = fromContainer[k], - toVal = toContainer[k]; + for(var k in fromContainer) { + var fromVal = fromContainer[k]; + var toVal = toContainer[k]; + if(toVal === fromVal) { + continue; + } if(k.charAt(0) === '_' || typeof fromVal === 'function') { // if it already exists at this point, it's something @@ -37,9 +37,15 @@ module.exports = function relinkPrivateKeys(toContainer, fromContainer) { } else if(isArray(fromVal) && isArray(toVal) && isPlainObject(fromVal[0])) { + // filter out data_array items that can contain user objects + // most of the time the toVal === fromVal check will catch these early + // but if the user makes new ones we also don't want to recurse in. + if(k === 'customdata' || k === 'ids') continue; + // recurse into arrays containers - for(var j = 0; j < fromVal.length; j++) { - if(isPlainObject(fromVal[j]) && isPlainObject(toVal[j])) { + var minLen = Math.min(fromVal.length, toVal.length); + for(var j = 0; j < minLen; j++) { + if((toVal[j] !== fromVal[j]) && isPlainObject(fromVal[j]) && isPlainObject(toVal[j])) { relinkPrivateKeys(toVal[j], fromVal[j]); } } diff --git a/src/plots/attributes.js b/src/plots/attributes.js index 4e2545aa015..1c05f27e26a 100644 --- a/src/plots/attributes.js +++ b/src/plots/attributes.js @@ -81,7 +81,8 @@ module.exports = { editType: 'calc', description: [ 'Assigns id labels to each datum.', - 'These ids for object constancy of data points during animation.' + 'These ids for object constancy of data points during animation.', + 'Should be an array of strings, not numbers or any other type.' ].join(' ') }, customdata: { diff --git a/tasks/util/make_schema.js b/tasks/util/make_schema.js index 85c5299e7c6..6b3537ff829 100644 --- a/tasks/util/make_schema.js +++ b/tasks/util/make_schema.js @@ -12,6 +12,7 @@ module.exports = function makeSchema(plotlyPath, schemaPath) { // package is annoying and platform-dependent. // see https://github.com/tmpvar/jsdom/issues/1782 w.HTMLCanvasElement.prototype.getContext = function() { return null; }; + w.URL.createObjectURL = function() { return null; }; w.eval(plotlyjsCode); diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index c91bb568de0..7ade3bc8ffe 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -1984,6 +1984,109 @@ describe('Test lib.js:', function() { }); }); }); + + describe('relinkPrivateKeys', function() { + it('ignores customdata and ids', function() { + var fromContainer = { + customdata: [{_x: 1, _y: 2, a: 3}], + ids: [{_i: 4, j: 5}] + }; + var toContainer = { + customdata: [{a: 6}], + ids: [{j: 7}] + }; + + Lib.relinkPrivateKeys(toContainer, fromContainer); + + expect(toContainer.customdata[0]._x).toBeUndefined(); + expect(toContainer.customdata[0]._y).toBeUndefined(); + expect(toContainer.ids[0]._i).toBeUndefined(); + }); + + it('ignores any values that are ===', function() { + var accesses = 0; + + var obj = { + get _x() { accesses++; return 1; }, + set _x(v) { accesses++; } + }; + var array = [obj]; + var array2 = [obj]; + + var fromContainer = { + x: array, + y: array, + o: obj + }; + var toContainer = { + x: array, + y: array2, + o: obj + }; + + Lib.relinkPrivateKeys(toContainer, fromContainer); + + expect(accesses).toBe(0); + + obj._x = 2; + expect(obj._x).toBe(1); + expect(accesses).toBe(2); + }); + + it('reinserts other private keys if they\'re not already there', function() { + var obj1 = {a: 10, _a: 11}; + var obj2 = {a: 12, _a: 13}; + function f1() { return 1; } + function f2() { return 2; } + + var fromContainer = { + a: 1, + _a: 2, + _b: 3, + _c: obj1, + _d: obj1, + f: f1, // functions are private even without _ + g: f1, + array: [{a: 3, _a: 4, _b: 5, f: f1, g: f1}], + o: {a: 6, _a: 7, _b: 8}, + array2: [{a: 9, _a: 10}], + o2: {a: 11, _a: 12} + }; + fromContainer._circular = fromContainer; + fromContainer._circular2 = fromContainer; + var toContainer = { + a: 21, + _a: 22, + _c: obj2, + f: f2, + array: [{a: 23, _a: 24, f: f2}], + o: {a: 26, _a: 27}, + x: [28], + _x: 29 + }; + toContainer._circular = toContainer; + + Lib.relinkPrivateKeys(toContainer, fromContainer); + + var expected = { + a: 21, + _a: 22, + _b: 3, + _c: obj2, + _circular: toContainer, + _circular2: fromContainer, + _d: obj1, + f: f2, + g: f1, + array: [{a: 23, _a: 24, _b: 5, f: f2, g: f1}], + o: {a: 26, _a: 27, _b: 8}, + x: [28], + _x: 29 + }; + + expect(toContainer).toEqual(expected); + }); + }); }); describe('Queue', function() {