diff --git a/lib/core/base/context.js b/lib/core/base/context.js index e6fb9e3824..0b366ce431 100644 --- a/lib/core/base/context.js +++ b/lib/core/base/context.js @@ -7,7 +7,8 @@ import { select, isNodeInContext, nodeSorter, - respondable + respondable, + clone } from '../utils'; /** @@ -230,6 +231,7 @@ function getRootNode({ include, exclude }) { * @param {Object} spec Configuration or "specification" object */ export default function Context(spec, flatTree) { + spec = clone(spec); this.frames = []; this.page = typeof spec?.page === 'boolean' ? spec.page : undefined; this.initiator = typeof spec?.initiator === 'boolean' ? spec.initiator : true; diff --git a/lib/core/utils/clone.js b/lib/core/utils/clone.js index fd1e337213..a880f192e3 100644 --- a/lib/core/utils/clone.js +++ b/lib/core/utils/clone.js @@ -8,6 +8,13 @@ function clone(obj) { var index, length, out = obj; + // DOM nodes cannot be cloned. + if ( + (window?.Node && obj instanceof window.Node) || + (window?.HTMLCollection && obj instanceof window.HTMLCollection) + ) { + return obj; + } if (obj !== null && typeof obj === 'object') { if (Array.isArray(obj)) { diff --git a/test/core/base/context.js b/test/core/base/context.js index 3442184069..3e68acae0b 100644 --- a/test/core/base/context.js +++ b/test/core/base/context.js @@ -14,6 +14,48 @@ describe('Context', function() { fixture.innerHTML = ''; }); + it('should not mutate exclude in input', function() { + fixture.innerHTML = '
'; + var context = { exclude: [['iframe', '#foo']] }; + // eslint-disable-next-line no-new + new Context(context); + assert.deepEqual(context, { exclude: [['iframe', '#foo']] }); + }); + + it('should not mutate its include input', function() { + fixture.innerHTML = ''; + var context = { include: [['#foo']] }; + // eslint-disable-next-line no-new + new Context(context); + assert.deepEqual(context, { include: [['#foo']] }); + }); + + it('should not share memory with complex object', function() { + fixture.innerHTML = ''; + var spec = { + include: [['#foo'], ['a']], + exclude: [['iframe', '#foo2']], + size: { width: 100, height: 100 } + }; + var context = new Context(spec); + assert.notStrictEqual(spec.include, context.include); + spec.include.forEach(function(_, index) { + assert.notStrictEqual(spec.include[index], context.include[index]); + }); + assert.notStrictEqual(spec.exclude, context.exclude); + spec.exclude.forEach(function(_, index) { + assert.notStrictEqual(spec.exclude[index], context.exclude[index]); + }); + assert.notStrictEqual(spec.size, context.size); + }); + + it('should not share memory with simple array', function() { + fixture.innerHTML = ''; + var spec = ['#foo']; + var context = new Context(spec); + assert.notStrictEqual(spec, context.include); + }); + describe('include', function() { it('should accept a single selector', function() { fixture.innerHTML = '';