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() {