Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit 5d97fd6

Browse files
author
Piotr Jasiun
authored
Merge pull request #1657 from ckeditor/t/1653
Fix: Stopped invoking `view.render()` by `EditingController` when the model document isn't changed. Closes #1653.
2 parents 2e04af7 + 0cb7c9f commit 5d97fd6

File tree

4 files changed

+105
-33
lines changed

4 files changed

+105
-33
lines changed

src/controller/editingcontroller.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,11 @@ export default class EditingController {
7878
this.view._renderingDisabled = true;
7979
}, { priority: 'highest' } );
8080

81-
this.listenTo( this.model, '_afterChanges', () => {
81+
this.listenTo( this.model, '_afterChanges', ( evt, { hasModelDocumentChanged } ) => {
8282
this.view._renderingDisabled = false;
83-
this.view.render();
83+
if ( hasModelDocumentChanged ) {
84+
this.view.render();
85+
}
8486
}, { priority: 'lowest' } );
8587

8688
// Whenever model document is changed, convert those changes to the view (using model.Document#differ).

src/model/document.js

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export default class Document {
4646
* The {@link module:engine/model/model~Model model} that the document is a part of.
4747
*
4848
* @readonly
49-
* @member {module:engine/model/model~Model}
49+
* @type {module:engine/model/model~Model}
5050
*/
5151
this.model = model;
5252

@@ -58,23 +58,23 @@ export default class Document {
5858
* a {@link module:utils/ckeditorerror~CKEditorError model-document-applyOperation-wrong-version} error is thrown.
5959
*
6060
* @readonly
61-
* @member {Number}
61+
* @type {Number}
6262
*/
6363
this.version = 0;
6464

6565
/**
6666
* The document's history.
6767
*
6868
* @readonly
69-
* @member {module:engine/model/history~History}
69+
* @type {module:engine/model/history~History}
7070
*/
7171
this.history = new History( this );
7272

7373
/**
7474
* The selection in this document.
7575
*
7676
* @readonly
77-
* @member {module:engine/model/documentselection~DocumentSelection}
77+
* @type {module:engine/model/documentselection~DocumentSelection}
7878
*/
7979
this.selection = new DocumentSelection( this );
8080

@@ -83,26 +83,34 @@ export default class Document {
8383
* {@link #getRoot} to manipulate it.
8484
*
8585
* @readonly
86-
* @member {module:utils/collection~Collection}
86+
* @type {module:utils/collection~Collection}
8787
*/
8888
this.roots = new Collection( { idProperty: 'rootName' } );
8989

9090
/**
9191
* The model differ object. Its role is to buffer changes done on the model document and then calculate a diff of those changes.
9292
*
9393
* @readonly
94-
* @member {module:engine/model/differ~Differ}
94+
* @type {module:engine/model/differ~Differ}
9595
*/
9696
this.differ = new Differ( model.markers );
9797

9898
/**
9999
* Post-fixer callbacks registered to the model document.
100100
*
101101
* @private
102-
* @member {Set}
102+
* @type {Set.<Function>}
103103
*/
104104
this._postFixers = new Set();
105105

106+
/**
107+
* A boolean indicates whether the selection has changed until
108+
*
109+
* @private
110+
* @type {Boolean}
111+
*/
112+
this._hasSelectionChangedFromTheLastChangeBlock = false;
113+
106114
// Graveyard tree root. Document always have a graveyard root, which stores removed nodes.
107115
this.createRoot( '$root', graveyardName );
108116

@@ -144,29 +152,8 @@ export default class Document {
144152
}, { priority: 'low' } );
145153

146154
// Listen to selection changes. If selection changed, mark it.
147-
let hasSelectionChanged = false;
148-
149155
this.listenTo( this.selection, 'change', () => {
150-
hasSelectionChanged = true;
151-
} );
152-
153-
// Wait for `_change` event from model, which signalizes that outermost change block has finished.
154-
// When this happens, check if there were any changes done on document, and if so, call post-fixers,
155-
// fire `change` event for features and conversion and then reset the differ.
156-
// Fire `change:data` event when at least one operation or buffered marker changes the data.
157-
this.listenTo( model, '_change', ( evt, writer ) => {
158-
if ( !this.differ.isEmpty || hasSelectionChanged ) {
159-
this._callPostFixers( writer );
160-
161-
if ( this.differ.hasDataChanges() ) {
162-
this.fire( 'change:data', writer.batch );
163-
} else {
164-
this.fire( 'change', writer.batch );
165-
}
166-
167-
this.differ.reset();
168-
hasSelectionChanged = false;
169-
}
156+
this._hasSelectionChangedFromTheLastChangeBlock = true;
170157
} );
171158

172159
// Buffer marker changes.
@@ -306,6 +293,44 @@ export default class Document {
306293
return json;
307294
}
308295

296+
/**
297+
* Check if there were any changes done on document, and if so, call post-fixers,
298+
* fire `change` event for features and conversion and then reset the differ.
299+
* Fire `change:data` event when at least one operation or buffered marker changes the data.
300+
*
301+
* @protected
302+
* @fires change
303+
* @fires change:data
304+
* @param {module:engine/model/writer~Writer writer} writer The writer on which post-fixers will be called.
305+
*/
306+
_handleChangeBlock( writer ) {
307+
if ( this._hasDocumentChangedFromTheLastChangeBlock() ) {
308+
this._callPostFixers( writer );
309+
310+
if ( this.differ.hasDataChanges() ) {
311+
this.fire( 'change:data', writer.batch );
312+
} else {
313+
this.fire( 'change', writer.batch );
314+
}
315+
316+
this.differ.reset();
317+
}
318+
319+
this._hasSelectionChangedFromTheLastChangeBlock = false;
320+
}
321+
322+
/**
323+
* Returns whether there is a buffered change or if the selection has changed from the last
324+
* {@link module:engine/model/model~Model#enqueueChange `enqueueChange()` block}
325+
* or {@link module:engine/model/model~Model#change `change()` block}.
326+
*
327+
* @protected
328+
* @returns {Boolean} Returns `true` if document has changed from the last `change()` or `enqueueChange()` block.
329+
*/
330+
_hasDocumentChangedFromTheLastChangeBlock() {
331+
return !this.differ.isEmpty || this._hasSelectionChangedFromTheLastChangeBlock;
332+
}
333+
309334
/**
310335
* Returns the default root for this document which is either the first root that was added to the document using
311336
* {@link #createRoot} or the {@link #graveyard graveyard root} if no other roots were created.
@@ -359,6 +384,7 @@ export default class Document {
359384
* Performs post-fixer loops. Executes post-fixer callbacks as long as none of them has done any changes to the model.
360385
*
361386
* @private
387+
* @param {module:engine/model/writer~Writer writer} writer The writer on which post-fixer callbacks will be called.
362388
*/
363389
_callPostFixers( writer ) {
364390
let wasFixed = false;

src/model/model.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,7 @@ export default class Model {
683683
*/
684684
_runPendingChanges() {
685685
const ret = [];
686+
let hasModelDocumentChanged = false;
686687

687688
this.fire( '_beforeChanges' );
688689

@@ -695,14 +696,19 @@ export default class Model {
695696
const callbackReturnValue = this._pendingChanges[ 0 ].callback( this._currentWriter );
696697
ret.push( callbackReturnValue );
697698

698-
// Fire internal `_change` event.
699+
// Collect an information whether the model document has changed during from the last pending change.
700+
hasModelDocumentChanged = hasModelDocumentChanged || this.document._hasDocumentChangedFromTheLastChangeBlock();
701+
702+
// Fire '_change' event before resetting differ.
699703
this.fire( '_change', this._currentWriter );
700704

705+
this.document._handleChangeBlock( this._currentWriter );
706+
701707
this._pendingChanges.shift();
702708
this._currentWriter = null;
703709
}
704710

705-
this.fire( '_afterChanges' );
711+
this.fire( '_afterChanges', { hasModelDocumentChanged } );
706712

707713
return ret;
708714
}
@@ -713,6 +719,7 @@ export default class Model {
713719
*
714720
* **Note:** This is an internal event! Use {@link module:engine/model/document~Document#event:change} instead.
715721
*
722+
* @deprecated
716723
* @protected
717724
* @event _change
718725
* @param {module:engine/model/writer~Writer} writer `Writer` instance that has been used in the change block.
@@ -732,6 +739,9 @@ export default class Model {
732739
*
733740
* @protected
734741
* @event _afterChanges
742+
* @param {Object} options
743+
* @param {Boolean} options.hasModelDocumentChanged `true` if the model document has changed during the
744+
* {@link module:engine/model/model~Model#change} or {@link module:engine/model/model~Model#enqueueChange} blocks.
735745
*/
736746

737747
/**

tests/tickets/1653.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md.
4+
*/
5+
6+
/* globals document */
7+
8+
import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
9+
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
10+
11+
describe( 'Bug ckeditor5-engine#1653', () => {
12+
it( '`DataController.parse()` should not invoke `editing.view.render()`', () => {
13+
let editor;
14+
15+
const element = document.createElement( 'div' );
16+
document.body.appendChild( element );
17+
18+
return ClassicTestEditor
19+
.create( element, { plugins: [ Paragraph ] } )
20+
.then( newEditor => {
21+
editor = newEditor;
22+
23+
const spy = sinon.spy( editor.editing.view, 'render' );
24+
editor.data.parse( '<p></p>' );
25+
26+
sinon.assert.notCalled( spy );
27+
} )
28+
.then( () => {
29+
element.remove();
30+
31+
return editor.destroy();
32+
} );
33+
} );
34+
} );

0 commit comments

Comments
 (0)