mutationobserver.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. import diff from '../../utils-diff.js';
  7. import Observer from './observer.js';
  8. import ViewElement from '../element.js';
  9. import ViewText from '../text.js';
  10. import objectUtils from '../../lib/lodash/object.js';
  11. import EmitterMixin from '../../emittermixin.js';
  12. export default class MutationObserver extends Observer {
  13. constructor() {
  14. super();
  15. this.config = {
  16. childList: true,
  17. characterData: true,
  18. characterDataOldValue: true,
  19. subtree: true
  20. };
  21. }
  22. /**
  23. * @method init
  24. * @param {treeView.TreeView}
  25. */
  26. init( treeView ) {
  27. this.treeView = treeView;
  28. this.domRoot = treeView.domRoot;
  29. this._mutationObserver = new window.MutationObserver( this._onMutations.bind( this ) );
  30. }
  31. /**
  32. * @method attach
  33. */
  34. attach() {
  35. this._mutationObserver.observe( this.domRoot, this.config );
  36. }
  37. /**
  38. * @method detach
  39. */
  40. detach() {
  41. this._mutationObserver.disconnect();
  42. }
  43. _onMutations( domMutations ) {
  44. // Use set for deduplication.
  45. const mutatedTexts = new Set();
  46. const mutatedElements = new Set();
  47. for ( let mutation of domMutations ) {
  48. if ( mutation.type === 'childList' ) {
  49. const element = ViewElement.getCorespondingElement( mutation.target );
  50. if ( element ) {
  51. mutatedElements.add( element );
  52. }
  53. }
  54. }
  55. for ( let mutation of domMutations ) {
  56. if ( mutation.type === 'characterData' ) {
  57. const text = ViewText.getCorespondingText( mutation.target );
  58. if ( text && !mutatedElements.has( text.parent ) ) {
  59. mutatedTexts.add( {
  60. type: 'text',
  61. oldText: text.getText(),
  62. newText: mutation.target.data,
  63. node: text
  64. } );
  65. }
  66. }
  67. }
  68. const viewMutations = [];
  69. for ( let mutatedText of mutatedTexts ) {
  70. mutatedText.node.markToSync( 'TEXT_NEEDS_UPDATE' );
  71. viewMutations.push( mutatedText );
  72. }
  73. for ( let viewElement of mutatedElements ) {
  74. const domElement = viewElement.domElement;
  75. const domChildren = domElement.childNodes;
  76. const viewChildren = viewElement.getChildren();
  77. const newViewChildren = [];
  78. for ( let domChild of domChildren ) {
  79. newViewChildren.push( ViewElement.createFromDom( domChild ) );
  80. }
  81. viewElement.markToSync( 'CHILDREN_NEED_UPDATE' );
  82. viewMutations.push( {
  83. type: 'childNodes',
  84. oldChildren: viewElement.getChildren(),
  85. newChildren: newViewChildren,
  86. diff: diff( viewChildren, newViewChildren, compareNodes ),
  87. node: viewElement
  88. } );
  89. }
  90. this.fire( 'mutations', viewMutations );
  91. this.treeView.render();
  92. function compareNodes( oldNode, newNode ) {
  93. // Elements.
  94. if ( oldNode instanceof ViewElement && newNode instanceof ViewElement ) {
  95. return oldNode === newNode;
  96. }
  97. // Texts.
  98. else if ( oldNode instanceof ViewText && newNode instanceof ViewText ) {
  99. return oldNode.getText() === newNode.getText();
  100. }
  101. // Not matching types.
  102. return false;
  103. }
  104. }
  105. }
  106. objectUtils.extend( MutationObserver.prototype, EmitterMixin );