areconnectedthroughproperties.js 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module utils/arestructuresconnected
  7. */
  8. /* globals EventTarget, Event */
  9. /**
  10. * Traverses both structures to find out whether there is a reference that is shared between both structures.
  11. *
  12. * @param {Object|Array} obj1
  13. * @param {Object|Array} obj2
  14. */
  15. export default function areConnectedThroughProperties( obj1, obj2 ) {
  16. if ( obj1 === obj2 && isObject( obj1 ) ) {
  17. return true;
  18. }
  19. const subNodes1 = getSubNodes( obj1 );
  20. const subNodes2 = getSubNodes( obj2 );
  21. for ( const node of subNodes1 ) {
  22. if ( subNodes2.has( node ) ) {
  23. return true;
  24. }
  25. }
  26. return false;
  27. }
  28. // Traverses JS structure and stores all sub-nodes, including the head node.
  29. // It walks into each iterable structures with the `try catch` block to omit errors that might be thrown during
  30. // tree walking. All primitives, functions and built-ins are skipped.
  31. function getSubNodes( head ) {
  32. const nodes = [ head ];
  33. // Nodes are stored to prevent infinite looping.
  34. const subNodes = new Set();
  35. while ( nodes.length > 0 ) {
  36. const node = nodes.shift();
  37. if ( subNodes.has( node ) || shouldNodeBeSkipped( node ) ) {
  38. continue;
  39. }
  40. subNodes.add( node );
  41. // Handle arrays, maps, sets, custom collections that implements `[ Symbol.iterator ]()`, etc.
  42. if ( node[ Symbol.iterator ] ) {
  43. // The custom editor iterators might cause some problems if the editor is crashed.
  44. try {
  45. nodes.push( ...node );
  46. } catch ( err ) {
  47. // eslint-disable-line no-empty
  48. }
  49. } else {
  50. nodes.push( ...Object.values( node ) );
  51. }
  52. }
  53. return subNodes;
  54. }
  55. function shouldNodeBeSkipped( node ) {
  56. const type = Object.prototype.toString.call( node );
  57. return (
  58. type === '[object Number]' ||
  59. type === '[object Boolean]' ||
  60. type === '[object String]' ||
  61. type === '[object Symbol]' ||
  62. type === '[object Function]' ||
  63. type === '[object Date]' ||
  64. type === '[object RegExp]' ||
  65. node === undefined ||
  66. node === null ||
  67. // Skip native DOM objects, e.g. Window, nodes, events, etc.
  68. node instanceof EventTarget ||
  69. node instanceof Event
  70. );
  71. }
  72. function isObject( structure ) {
  73. return typeof structure === 'object' && structure !== null;
  74. }