8
0
Просмотр исходного кода

Split Watchdog class into the base Watchdog class and derived from it EditorWatchdog class.

Maciej Bukowski 6 лет назад
Родитель
Сommit
30834e7d08

+ 321 - 0
packages/ckeditor5-watchdog/src/editorwatchdog.js

@@ -0,0 +1,321 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module watchdog/editorwatchdog
+ */
+
+/* globals console */
+
+import mix from '@ckeditor/ckeditor5-utils/src/mix';
+import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
+import { throttle, cloneDeepWith, isElement } from 'lodash-es';
+import areConnectedThroughProperties from '@ckeditor/ckeditor5-utils/src/areconnectedthroughproperties';
+import Watchdog from './watchdog';
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+
+/**
+ * A watchdog for CKEditor 5 editors.
+ *
+ * See the {@glink features/watchdog Watchdog feature guide} to learn the rationale behind it and
+ * how to use it.
+ */
+export default class EditorWatchdog extends Watchdog {
+	/**
+	 * @param {module:watchdog/watchdog~WatchdogConfig} [config] The watchdog plugin configuration.
+	 */
+	constructor( config = {} ) {
+		super( config );
+
+		/**
+		 * The current editor instance.
+		 *
+		 * @private
+		 * @type {module:core/editor/editor~Editor}
+		 */
+		this._editor = null;
+
+		/**
+		 * Throttled save method. The `save()` method is called the specified `saveInterval` after `throttledSave()` is called,
+		 * unless a new action happens in the meantime.
+		 *
+		 * @private
+		 * @type {Function}
+		 */
+		this._throttledSave = throttle(
+			this._save.bind( this ),
+			typeof config.saveInterval === 'number' ? config.saveInterval : 5000
+		);
+
+		/**
+		 * The latest saved editor data represented as a root name -> root data object.
+		 *
+		 * @private
+		 * @member {Object.<String,String>} #_data
+		 */
+
+		/**
+		 * The last document version.
+		 *
+		 * @private
+		 * @member {Number} #_lastDocumentVersion
+		 */
+
+		/**
+		 * The editor source element or data.
+		 *
+		 * @private
+		 * @member {HTMLElement|String|Object.<String|String>} #_elementOrData
+		 */
+
+		/**
+		 * The editor configuration.
+		 *
+		 * @private
+		 * @member {Object|undefined} #_config
+		 */
+
+		this._destructor = editor => editor.destroy();
+	}
+
+	/**
+	 * The current editor instance.
+	 *
+	 * @readonly
+	 * @type {module:core/editor/editor~Editor}
+	 */
+	get editor() {
+		return this._editor;
+	}
+
+	/**
+	 * Sets the function that is responsible for the editor creation.
+	 * It expects a function that should return a promise.
+	 *
+	 *		watchdog.setCreator( ( element, config ) => ClassicEditor.create( element, config ) );
+	 *
+	 * @abstract
+	 * @method setCreator
+	 * @param {Function} creator
+	 */
+
+	/**
+	 * Sets the function that is responsible for the editor destruction.
+	 * Overrides the default destruction function, which destroys only the editor instance.
+	 * It expects a function that should return a promise or `undefined`.
+	 *
+	 *		watchdog.setDestructor( editor => {
+	 *			// Do something before the editor is destroyed.
+	 *
+	 *			return editor
+	 *				.destroy()
+	 *				.then( () => {
+	 *					// Do something after the editor is destroyed.
+	 *				} );
+	 *		} );
+	 *
+	 * @abstract
+	 * @method setDestructor
+	 * @param {Function} destructor
+	 */
+
+	/**
+	 * Creates a watched editor instance using the creator passed to the {@link #setCreator `setCreator()`} method or
+	 * the {@link module:watchdog/watchdog~Watchdog.for `Watchdog.for()`} helper.
+	 *
+	 * @param {HTMLElement|String|Object.<String|String>} elementOrData
+	 * @param {module:core/editor/editorconfig~EditorConfig} [config]
+	 *
+	 * @returns {Promise}
+	 */
+	async create( elementOrData, config ) {
+		if ( !this._creator ) {
+			/**
+			 * The watchdog's editor creator is not defined. Define it by using
+			 * {@link module:watchdog/watchdog~Watchdog#setCreator `Watchdog#setCreator()`} or
+			 * the {@link module:watchdog/watchdog~Watchdog.for `Watchdog.for()`} helper.
+			 *
+			 * @error watchdog-creator-not-defined
+			 */
+			throw new CKEditorError(
+				'watchdog-creator-not-defined: The watchdog\'s editor creator is not defined.',
+				null
+			);
+		}
+
+		super._startErrorHandling();
+
+		this._elementOrData = elementOrData;
+
+		// Clone configuration because it might be shared within multiple watchdog instances. Otherwise,
+		// when an error occurs in one of these editors, the watchdog will restart all of them.
+		this._config = cloneDeepWith( config, value => {
+			// Leave DOM references.
+			return isElement( value ) ? value : undefined;
+		} );
+
+		const editor = await this._creator( elementOrData, this._config );
+
+		this._editor = editor;
+
+		this.listenTo( editor.model.document, 'change:data', this._throttledSave );
+
+		this._lastDocumentVersion = editor.model.document.version;
+		this._data = this._getData();
+
+		this.state = 'ready';
+	}
+
+	/**
+	 * Destroys the current editor instance by using the destructor passed to the {@link #setDestructor `setDestructor()`} method
+	 * and sets state to `destroyed`.
+	 *
+	 * @returns {Promise}
+	 */
+	async destroy() {
+		this.state = 'destroyed';
+
+		return this._destroy();
+	}
+
+	async _destroy() {
+		this._stopErrorHandling();
+		// Save data if there is a remaining editor data change.
+		this._throttledSave.flush();
+
+		await this._destructor( this._editor );
+
+		this._editor = null;
+	}
+
+	/**
+	 * Saves the editor data, so it can be restored after the crash even if the data cannot be fetched at
+	 * the moment of the crash.
+	 *
+	 * @private
+	 */
+	_save() {
+		const version = this._editor.model.document.version;
+
+		// Change may not produce an operation, so the document's version
+		// can be the same after that change.
+		if ( version === this._lastDocumentVersion ) {
+			return;
+		}
+
+		try {
+			this._data = this._getData();
+			this._lastDocumentVersion = version;
+		} catch ( err ) {
+			console.error(
+				err,
+				'An error happened during restoring editor data. ' +
+				'Editor will be restored from the previously saved data.'
+			);
+		}
+	}
+
+	/**
+	 * Returns the editor data.
+	 *
+	 * @private
+	 * @returns {Object<String,String>}
+	 */
+	_getData() {
+		const data = {};
+
+		for ( const rootName of this._editor.model.document.getRootNames() ) {
+			data[ rootName ] = this._editor.data.get( { rootName } );
+		}
+
+		return data;
+	}
+
+	/**
+	 * Restarts the editor instance. This method is called whenever an editor error occurs. It fires the `restart` event and changes
+	 * the state to `initializing`.
+	 *
+	 * @public
+	 * @fires restart
+	 * @returns {Promise}
+	 */
+	async restart() {
+		this.state = 'initializing';
+
+		try {
+			await this._destroy();
+		} catch ( err ) {
+			console.error( 'An error happened during the editor destructing.', err );
+		}
+
+		if ( typeof this._elementOrData === 'string' ) {
+			await this.create( this._data, this._config );
+		} else {
+			const updatedConfig = Object.assign( {}, this._config, {
+				initialData: this._data
+			} );
+
+			await this.create( this._elementOrData, updatedConfig );
+		}
+
+		this.fire( 'restart' );
+	}
+
+	/**
+	 * Traverses both structures to find out whether the error context is connected
+	 * with the current editor.
+	 *
+	 * @private
+	 * @param {module:utils/ckeditorerror~CKEditorError} error
+	 */
+	_isErrorComingFromThisInstance( error ) {
+		return areConnectedThroughProperties( this._editor, error.context );
+	}
+
+	/**
+	 * A shorthand method for creating an instance of the watchdog. For the full usage, see the
+	 * {@link ~Watchdog `Watchdog` class description}.
+	 *
+	 * Usage:
+	 *
+	 *		const watchdog = Watchdog.for( ClassicEditor );
+	 *
+	 *		watchdog.create( elementOrData, config );
+	 *
+	 * @param {*} Editor The editor class.
+	 * @param {module:watchdog/watchdog~WatchdogConfig} [watchdogConfig] The watchdog plugin configuration.
+	 */
+	static for( Editor, watchdogConfig ) {
+		const watchdog = new this( watchdogConfig );
+
+		watchdog.setCreator( ( elementOrData, config ) => Editor.create( elementOrData, config ) );
+
+		return watchdog;
+	}
+
+	/**
+	 * Fired after the watchdog restarts the error in case of a crash.
+	 *
+	 * @event restart
+	 */
+}
+
+mix( Watchdog, ObservableMixin );
+
+/**
+ * The watchdog plugin configuration.
+ *
+ * @typedef {Object} WatchdogConfig
+ *
+ * @property {Number} [crashNumberLimit=3] A threshold specifying the number of editor crashes
+ * when the watchdog stops restarting the editor in case of errors.
+ * After this limit is reached and the time between last errors is shorter than `minimumNonErrorTimePeriod`
+ * the watchdog changes its state to `crashedPermanently` and it stops restarting the editor. This prevents an infinite restart loop.
+ * @property {Number} [minimumNonErrorTimePeriod=5000] An average amount of milliseconds between last editor errors
+ * (defaults to 5000). When the period of time between errors is lower than that and the `crashNumberLimit` is also reached
+ * the watchdog changes its state to `crashedPermanently` and it stops restarting the editor. This prevents an infinite restart loop.
+ * @property {Number} [saveInterval=5000] A minimum number of milliseconds between saving editor data internally, (defaults to 5000).
+ * Note that for large documents this might have an impact on the editor performance.
+ */

+ 22 - 277
packages/ckeditor5-watchdog/src/watchdog.js

@@ -7,19 +7,18 @@
  * @module watchdog/watchdog
  */
 
-/* globals console, window */
+/* globals window */
 
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
 import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
-import { throttle, cloneDeepWith, isElement } from 'lodash-es';
-import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
-import areConnectedThroughProperties from '@ckeditor/ckeditor5-utils/src/areconnectedthroughproperties';
 
 /**
- * A watchdog for CKEditor 5 editors.
+ * A base watchdog class.
  *
  * See the {@glink features/watchdog Watchdog feature guide} to learn the rationale behind it and
  * how to use it.
+ *
+ * @abstract
  */
 export default class Watchdog {
 	/**
@@ -59,7 +58,7 @@ export default class Watchdog {
 		this.set( 'state', 'initializing' );
 
 		/**
-		 * @private
+		 * @protected
 		 * @type {Number}
 		 * @see module:watchdog/watchdog~WatchdogConfig
 		 */
@@ -74,7 +73,7 @@ export default class Watchdog {
 		this._now = Date.now;
 
 		/**
-		 * @private
+		 * @protected
 		 * @type {Number}
 		 * @see module:watchdog/watchdog~WatchdogConfig
 		 */
@@ -99,87 +98,23 @@ export default class Watchdog {
 		};
 
 		/**
-		 * Throttled save method. The `save()` method is called the specified `saveInterval` after `throttledSave()` is called,
-		 * unless a new action happens in the meantime.
-		 *
-		 * @private
-		 * @type {Function}
-		 */
-		this._throttledSave = throttle(
-			this._save.bind( this ),
-			typeof config.saveInterval === 'number' ? config.saveInterval : 5000
-		);
-
-		/**
-		 * The current editor instance.
-		 *
-		 * @private
-		 * @type {module:core/editor/editor~Editor}
-		 */
-		this._editor = null;
-
-		/**
-		 * The editor creation method.
+		 * The creation method.
 		 *
-		 * @private
+		 * @protected
 		 * @member {Function} #_creator
 		 * @see #setCreator
 		 */
 
 		/**
-		 * The editor destruction method.
+		 * The destruction method.
 		 *
-		 * @private
+		 * @protected
 		 * @member {Function} #_destructor
 		 * @see #setDestructor
 		 */
-		this._destructor = editor => editor.destroy();
-
-		/**
-		 * The latest saved editor data represented as a root name -> root data object.
-		 *
-		 * @private
-		 * @member {Object.<String,String>} #_data
-		 */
-
-		/**
-		 * The last document version.
-		 *
-		 * @private
-		 * @member {Number} #_lastDocumentVersion
-		 */
-
-		/**
-		 * The editor source element or data.
-		 *
-		 * @private
-		 * @member {HTMLElement|String|Object.<String|String>} #_elementOrData
-		 */
-
-		/**
-		 * The editor configuration.
-		 *
-		 * @private
-		 * @member {Object|undefined} #_config
-		 */
-	}
-
-	/**
-	 * The current editor instance.
-	 *
-	 * @readonly
-	 * @type {module:core/editor/editor~Editor}
-	 */
-	get editor() {
-		return this._editor;
 	}
 
 	/**
-	 * Sets the function that is responsible for the editor creation.
-	 * It expects a function that should return a promise.
-	 *
-	 *		watchdog.setCreator( ( element, config ) => ClassicEditor.create( element, config ) );
-	 *
 	 * @param {Function} creator
 	 */
 	setCreator( creator ) {
@@ -187,20 +122,6 @@ export default class Watchdog {
 	}
 
 	/**
-	 * Sets the function that is responsible for the editor destruction.
-	 * Overrides the default destruction function, which destroys only the editor instance.
-	 * It expects a function that should return a promise or `undefined`.
-	 *
-	 *		watchdog.setDestructor( editor => {
-	 *			// Do something before the editor is destroyed.
-	 *
-	 *			return editor
-	 *				.destroy()
-	 *				.then( () => {
-	 *					// Do something after the editor is destroyed.
-	 *				} );
-	 *		} );
-	 *
 	 * @param {Function} destructor
 	 */
 	setDestructor( destructor ) {
@@ -208,136 +129,28 @@ export default class Watchdog {
 	}
 
 	/**
-	 * Creates a watched editor instance using the creator passed to the {@link #setCreator `setCreator()`} method or
-	 * the {@link module:watchdog/watchdog~Watchdog.for `Watchdog.for()`} helper.
-	 *
-	 * @param {HTMLElement|String|Object.<String|String>} elementOrData
-	 * @param {module:core/editor/editorconfig~EditorConfig} [config]
-	 *
+	 * @protected
 	 * @returns {Promise}
 	 */
-	create( elementOrData, config ) {
-		if ( !this._creator ) {
-			/**
-			 * The watchdog's editor creator is not defined. Define it by using
-			 * {@link module:watchdog/watchdog~Watchdog#setCreator `Watchdog#setCreator()`} or
-			 * the {@link module:watchdog/watchdog~Watchdog.for `Watchdog.for()`} helper.
-			 *
-			 * @error watchdog-creator-not-defined
-			 */
-			throw new CKEditorError(
-				'watchdog-creator-not-defined: The watchdog\'s editor creator is not defined.',
-				null
-			);
-		}
-
-		this._elementOrData = elementOrData;
-
-		// Clone configuration because it might be shared within multiple watchdog instances. Otherwise,
-		// when an error occurs in one of these editors, the watchdog will restart all of them.
-		this._config = cloneDeepWith( config, value => {
-			// Leave DOM references.
-			return isElement( value ) ? value : undefined;
-		} );
-
-		return Promise.resolve()
-			.then( () => this._creator( elementOrData, this._config ) )
-			.then( editor => {
-				this._editor = editor;
-
-				window.addEventListener( 'error', this._boundErrorHandler );
-				window.addEventListener( 'unhandledrejection', this._boundErrorHandler );
-
-				this.listenTo( editor.model.document, 'change:data', this._throttledSave );
-
-				this._lastDocumentVersion = editor.model.document.version;
-
-				this._data = this._getData();
-				this.state = 'ready';
-			} );
+	async _startErrorHandling() {
+		window.addEventListener( 'error', this._boundErrorHandler );
+		window.addEventListener( 'unhandledrejection', this._boundErrorHandler );
 	}
 
 	/**
-	 * Destroys the current editor instance by using the destructor passed to the {@link #setDestructor `setDestructor()`} method
-	 * and sets state to `destroyed`.
-	 *
+	 * @protected
 	 * @returns {Promise}
 	 */
-	destroy() {
-		this.state = 'destroyed';
-
-		return this._destroy();
-	}
-
-	/**
-	 * Destroys the current editor instance by using the destructor passed to the {@link #setDestructor `setDestructor()`} method.
-	 *
-	 * @private
-	 */
-	_destroy() {
+	async _stopErrorHandling() {
 		window.removeEventListener( 'error', this._boundErrorHandler );
 		window.removeEventListener( 'unhandledrejection', this._boundErrorHandler );
-
-		this.stopListening( this._editor.model.document, 'change:data', this._throttledSave );
-
-		// Save data if there is a remaining editor data change.
-		this._throttledSave.flush();
-
-		return Promise.resolve()
-			.then( () => this._destructor( this._editor ) )
-			.then( () => {
-				this._editor = null;
-			} );
-	}
-
-	/**
-	 * Saves the editor data, so it can be restored after the crash even if the data cannot be fetched at
-	 * the moment of the crash.
-	 *
-	 * @private
-	 */
-	_save() {
-		const version = this._editor.model.document.version;
-
-		// Change may not produce an operation, so the document's version
-		// can be the same after that change.
-		if ( version === this._lastDocumentVersion ) {
-			return;
-		}
-
-		try {
-			this._data = this._getData();
-			this._lastDocumentVersion = version;
-		} catch ( err ) {
-			console.error(
-				err,
-				'An error happened during restoring editor data. ' +
-				'Editor will be restored from the previously saved data.'
-			);
-		}
-	}
-
-	/**
-	 * Returns the editor data.
-	 *
-	 * @private
-	 * @returns {Object<String,String>}
-	 */
-	_getData() {
-		const data = {};
-
-		for ( const rootName of this._editor.model.document.getRootNames() ) {
-			data[ rootName ] = this._editor.data.get( { rootName } );
-		}
-
-		return data;
 	}
 
 	/**
 	 * Checks if the error comes from the editor that is handled by the watchdog (by checking the error context) and
 	 * restarts the editor. It reacts to {@link module:utils/ckeditorerror~CKEditorError `CKEditorError` errors} only.
 	 *
-	 * @protected
+	 * @private
 	 * @fires error
 	 * @param {Error} error Error.
 	 * @param {ErrorEvent|PromiseRejectionEvent} evt Error event.
@@ -362,8 +175,8 @@ export default class Watchdog {
 			this.fire( 'error', { error } );
 			this.state = 'crashed';
 
-			if ( this._shouldRestartEditor() ) {
-				this._restart();
+			if ( this._shouldRestart() ) {
+				this.restart();
 			} else {
 				this.state = 'crashedPermanently';
 			}
@@ -389,14 +202,14 @@ export default class Watchdog {
 			// Do not react to errors if the watchdog is in states other than `ready`.
 			this.state === 'ready' &&
 
-			this._isErrorComingFromThisEditor( error )
+			this._isErrorComingFromThisInstance( error )
 		);
 	}
 
 	/**
-	 * Checks if the editor should be restared or if it should be marked as crashed.
+	 * Checks if the editor should be restarted or if it should be marked as crashed.
 	 */
-	_shouldRestartEditor() {
+	_shouldRestart() {
 		if ( this.crashes.length <= this._crashNumberLimit ) {
 			return true;
 		}
@@ -409,80 +222,12 @@ export default class Watchdog {
 		return averageNonErrorTimePeriod > this._minimumNonErrorTimePeriod;
 	}
 
-	/**
-	 * Restarts the editor instance. This method is called whenever an editor error occurs. It fires the `restart` event and changes
-	 * the state to `initializing`.
-	 *
-	 * @private
-	 * @fires restart
-	 * @returns {Promise}
-	 */
-	_restart() {
-		this.state = 'initializing';
-
-		return Promise.resolve()
-			.then( () => this._destroy() )
-			.catch( err => console.error( 'An error happened during the editor destructing.', err ) )
-			.then( () => {
-				if ( typeof this._elementOrData === 'string' ) {
-					return this.create( this._data, this._config );
-				}
-
-				const updatedConfig = Object.assign( {}, this._config, {
-					initialData: this._data
-				} );
-
-				return this.create( this._elementOrData, updatedConfig );
-			} )
-			.then( () => {
-				this.fire( 'restart' );
-			} );
-	}
-
-	/**
-	 * Traverses both structures to find out whether the error context is connected
-	 * with the current editor.
-	 *
-	 * @private
-	 * @param {module:utils/ckeditorerror~CKEditorError} error
-	 */
-	_isErrorComingFromThisEditor( error ) {
-		return areConnectedThroughProperties( this._editor, error.context );
-	}
-
-	/**
-	 * A shorthand method for creating an instance of the watchdog. For the full usage, see the
-	 * {@link ~Watchdog `Watchdog` class description}.
-	 *
-	 * Usage:
-	 *
-	 *		const watchdog = Watchdog.for( ClassicEditor );
-	 *
-	 *		watchdog.create( elementOrData, config );
-	 *
-	 * @param {*} Editor The editor class.
-	 * @param {module:watchdog/watchdog~WatchdogConfig} [watchdogConfig] The watchdog plugin configuration.
-	 */
-	static for( Editor, watchdogConfig ) {
-		const watchdog = new Watchdog( watchdogConfig );
-
-		watchdog.setCreator( ( elementOrData, config ) => Editor.create( elementOrData, config ) );
-
-		return watchdog;
-	}
-
 	/**
 	 * Fired when a new {@link module:utils/ckeditorerror~CKEditorError `CKEditorError`} error connected to the watchdog editor occurs
 	 * and the watchdog will react to it.
 	 *
 	 * @event error
 	 */
-
-	/**
-	 * Fired after the watchdog restarts the error in case of a crash.
-	 *
-	 * @event restart
-	 */
 }
 
 mix( Watchdog, ObservableMixin );

+ 71 - 75
packages/ckeditor5-watchdog/tests/watchdog.js → packages/ckeditor5-watchdog/tests/editorwatchdog.js

@@ -5,15 +5,15 @@
 
 /* globals setTimeout, window, console, document */
 
-import Watchdog from '../src/watchdog';
+import EditorWatchdog from '../src/editorwatchdog';
 import Editor from '@ckeditor/ckeditor5-core/src/editor/editor';
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
-import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
+import { assertCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
 import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
 
-describe( 'Watchdog', () => {
+describe( 'EditorWatchdog', () => {
 	let element;
 
 	beforeEach( () => {
@@ -27,39 +27,37 @@ describe( 'Watchdog', () => {
 	} );
 
 	describe( 'create()', () => {
-		it( 'should create an editor instance', () => {
-			const watchdog = new Watchdog();
+		it( 'should create an editor instance', async () => {
+			const watchdog = new EditorWatchdog();
 
 			const editorCreateSpy = sinon.spy( ClassicTestEditor, 'create' );
 			const editorDestroySpy = sinon.spy( ClassicTestEditor.prototype, 'destroy' );
 
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
 
-			return watchdog.create( element, {} )
-				.then( () => {
-					sinon.assert.calledOnce( editorCreateSpy );
-					sinon.assert.notCalled( editorDestroySpy );
+			await watchdog.create( element, {} );
 
-					return watchdog.destroy();
-				} )
-				.then( () => {
-					sinon.assert.calledOnce( editorCreateSpy );
-					sinon.assert.calledOnce( editorDestroySpy );
-				} );
+			sinon.assert.calledOnce( editorCreateSpy );
+			sinon.assert.notCalled( editorDestroySpy );
+
+			await watchdog.destroy();
+
+			sinon.assert.calledOnce( editorCreateSpy );
+			sinon.assert.calledOnce( editorDestroySpy );
 		} );
 
-		it( 'should throw an error when the creator is not defined', () => {
-			const watchdog = new Watchdog();
+		it( 'should throw an error when the creator is not defined', async () => {
+			const watchdog = new EditorWatchdog();
 
-			expectToThrowCKEditorError(
-				() => watchdog.create(),
-				/^watchdog-creator-not-defined/,
-				null
-			);
+			try {
+				await watchdog.create();
+			} catch ( err ) {
+				assertCKEditorError( err, /^watchdog-creator-not-defined/, null );
+			}
 		} );
 
-		it( 'should properly copy the config', () => {
-			const watchdog = new Watchdog();
+		it( 'should properly copy the config', async () => {
+			const watchdog = new EditorWatchdog();
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
 
 			const config = {
@@ -67,16 +65,16 @@ describe( 'Watchdog', () => {
 				bar: document.createElement( 'div' )
 			};
 
-			return watchdog.create( element, config ).then( () => {
-				expect( watchdog.editor.config._config.foo ).to.not.equal( config.foo );
-				expect( watchdog.editor.config._config.bar ).to.equal( config.bar );
+			await watchdog.create( element, config );
 
-				return watchdog.destroy();
-			} );
+			expect( watchdog.editor.config._config.foo ).to.not.equal( config.foo );
+			expect( watchdog.editor.config._config.bar ).to.equal( config.bar );
+
+			await watchdog.destroy();
 		} );
 
-		it( 'should support editor data passed as the first argument', () => {
-			const watchdog = new Watchdog();
+		it( 'should support editor data passed as the first argument', async () => {
+			const watchdog = new EditorWatchdog();
 
 			watchdog.setCreator( ( data, config ) => ClassicTestEditor.create( data, config ) );
 
@@ -85,30 +83,28 @@ describe( 'Watchdog', () => {
 			const windowErrorSpy = sinon.spy();
 			window.onerror = windowErrorSpy;
 
-			return watchdog.create( '<p>foo</p>', { plugins: [ Paragraph ] } )
-				.then( () => {
-					expect( watchdog.editor.getData() ).to.equal( '<p>foo</p>' );
+			await watchdog.create( '<p>foo</p>', { plugins: [ Paragraph ] } );
 
-					return new Promise( res => {
-						setTimeout( () => throwCKEditorError( 'foo', watchdog.editor ) );
+			expect( watchdog.editor.getData() ).to.equal( '<p>foo</p>' );
 
-						watchdog.on( 'restart', () => {
-							window.onerror = originalErrorHandler;
-							res();
-						} );
-					} );
-				} )
-				.then( () => {
-					expect( watchdog.editor.getData() ).to.equal( '<p>foo</p>' );
+			await new Promise( res => {
+				setTimeout( () => throwCKEditorError( 'foo', watchdog.editor ) );
 
-					return watchdog.destroy();
+				watchdog.on( 'restart', () => {
+					window.onerror = originalErrorHandler;
+					res();
 				} );
+			} );
+
+			expect( watchdog.editor.getData() ).to.equal( '<p>foo</p>' );
+
+			await watchdog.destroy();
 		} );
 	} );
 
 	describe( 'editor', () => {
 		it( 'should be the current editor instance', () => {
-			const watchdog = Watchdog.for( ClassicTestEditor );
+			const watchdog = EditorWatchdog.for( ClassicTestEditor );
 
 			// sinon.stub( window, 'onerror' ).value( undefined ); and similar do not work.
 			const originalErrorHandler = window.onerror;
@@ -147,7 +143,7 @@ describe( 'Watchdog', () => {
 
 	describe( 'error handling', () => {
 		it( 'Watchdog should not restart editor during the initialization', () => {
-			const watchdog = new Watchdog();
+			const watchdog = new EditorWatchdog();
 
 			watchdog.setCreator( el =>
 				ClassicTestEditor.create( el )
@@ -166,7 +162,7 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'Watchdog should not restart editor during the destroy', () => {
-			const watchdog = new Watchdog();
+			const watchdog = new EditorWatchdog();
 
 			watchdog.setCreator( el => ClassicTestEditor.create( el ) );
 			watchdog.setDestructor( () => Promise.reject( new Error( 'foo' ) ) );
@@ -186,7 +182,7 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'Watchdog should not hide intercepted errors', () => {
-			const watchdog = new Watchdog();
+			const watchdog = new EditorWatchdog();
 
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
 
@@ -214,7 +210,7 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'Watchdog should intercept editor errors and restart the editor during the runtime', () => {
-			const watchdog = new Watchdog();
+			const watchdog = new EditorWatchdog();
 
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
 
@@ -236,7 +232,7 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'Watchdog should not intercept non-editor errors', () => {
-			const watchdog = new Watchdog();
+			const watchdog = new EditorWatchdog();
 
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
 
@@ -281,8 +277,8 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'Watchdog should not intercept other editor errors', () => {
-			const watchdog1 = Watchdog.for( ClassicTestEditor );
-			const watchdog2 = Watchdog.for( ClassicTestEditor );
+			const watchdog1 = EditorWatchdog.for( ClassicTestEditor );
+			const watchdog2 = EditorWatchdog.for( ClassicTestEditor );
 
 			const config = {
 				plugins: []
@@ -319,7 +315,7 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'Watchdog should intercept editor errors and restart the editor if the editor can be found from the context', () => {
-			const watchdog = new Watchdog();
+			const watchdog = new EditorWatchdog();
 
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
 
@@ -341,7 +337,7 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'Watchdog should intercept editor errors and restart the editor if the editor can be found from the context #2', () => {
-			const watchdog = new Watchdog();
+			const watchdog = new EditorWatchdog();
 
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
 
@@ -373,7 +369,7 @@ describe( 'Watchdog', () => {
 
 		it( 'Watchdog should crash permanently if the `crashNumberLimit` is reached' +
 			' and the average time between errors is lower than `minimumNonErrorTimePeriod` (default values)', () => {
-			const watchdog = new Watchdog();
+			const watchdog = new EditorWatchdog();
 
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
 
@@ -409,7 +405,7 @@ describe( 'Watchdog', () => {
 
 		it( 'Watchdog should crash permanently if the `crashNumberLimit` is reached' +
 			' and the average time between errors is lower than `minimumNonErrorTimePeriod` (custom values)', () => {
-			const watchdog = new Watchdog( { crashNumberLimit: 2, minimumNonErrorTimePeriod: 1000 } );
+			const watchdog = new EditorWatchdog( { crashNumberLimit: 2, minimumNonErrorTimePeriod: 1000 } );
 
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
 
@@ -444,7 +440,7 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'Watchdog should not crash permantently when average time between errors is longer than `minimumNonErrorTimePeriod`', () => {
-			const watchdog = new Watchdog( { crashNumberLimit: 2, minimumNonErrorTimePeriod: 0 } );
+			const watchdog = new EditorWatchdog( { crashNumberLimit: 2, minimumNonErrorTimePeriod: 0 } );
 
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
 
@@ -479,7 +475,7 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'Watchdog should warn if the CKEditorError missing its context', () => {
-			const watchdog = new Watchdog();
+			const watchdog = new EditorWatchdog();
 
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
 			watchdog.setDestructor( editor => editor.destroy() );
@@ -511,7 +507,7 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'Watchdog should omit error if the CKEditorError context is equal to null', () => {
-			const watchdog = new Watchdog();
+			const watchdog = new EditorWatchdog();
 
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
 
@@ -535,7 +531,7 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'editor should be restarted with the data before the crash #1', () => {
-			const watchdog = new Watchdog();
+			const watchdog = new EditorWatchdog();
 
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
 
@@ -562,7 +558,7 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'editor should be restarted with the data before the crash #2', () => {
-			const watchdog = new Watchdog();
+			const watchdog = new EditorWatchdog();
 
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
 
@@ -595,13 +591,13 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'editor should be restarted with the data of the latest document version before the crash', () => {
-			const watchdog = new Watchdog();
+			const watchdog = new EditorWatchdog();
 
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
 
 			// sinon.stub( window, 'onerror' ).value( undefined ); and similar do not work.
 			const originalErrorHandler = window.onerror;
-			window.onerror = undefined;
+			window.onerror = sinon.spy();
 
 			return watchdog.create( element, {
 				initialData: '<p>foo</p>',
@@ -633,7 +629,7 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'editor should be restarted with the latest available data before the crash', () => {
-			const watchdog = new Watchdog();
+			const watchdog = new EditorWatchdog();
 
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
 
@@ -693,7 +689,7 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'should use the custom destructor if passed', () => {
-			const watchdog = new Watchdog();
+			const watchdog = new EditorWatchdog();
 			const destructionSpy = sinon.spy();
 
 			watchdog.setCreator( ( el, config ) => ClassicTestEditor.create( el, config ) );
@@ -753,7 +749,7 @@ describe( 'Watchdog', () => {
 				return;
 			}
 
-			const watchdog = Watchdog.for( ClassicTestEditor );
+			const watchdog = EditorWatchdog.for( ClassicTestEditor );
 			const originalErrorHandler = window.onerror;
 
 			window.onerror = undefined;
@@ -784,7 +780,7 @@ describe( 'Watchdog', () => {
 				return;
 			}
 
-			const watchdog = Watchdog.for( ClassicTestEditor );
+			const watchdog = EditorWatchdog.for( ClassicTestEditor );
 			const originalErrorHandler = window.onerror;
 			const editorErrorSpy = sinon.spy();
 
@@ -819,7 +815,7 @@ describe( 'Watchdog', () => {
 			// This will ensure that the second data save action will be put off in time.
 			const SAVE_INTERVAL = 30;
 
-			const watchdog = Watchdog.for( ClassicTestEditor, {
+			const watchdog = EditorWatchdog.for( ClassicTestEditor, {
 				saveInterval: SAVE_INTERVAL,
 			} );
 
@@ -852,7 +848,7 @@ describe( 'Watchdog', () => {
 
 	describe( 'static for()', () => {
 		it( 'should be a shortcut method for creating the watchdog', () => {
-			const watchdog = Watchdog.for( ClassicTestEditor );
+			const watchdog = EditorWatchdog.for( ClassicTestEditor );
 
 			// sinon.stub( window, 'onerror' ).value( undefined ); and similar do not work.
 			const originalErrorHandler = window.onerror;
@@ -884,7 +880,7 @@ describe( 'Watchdog', () => {
 
 	describe( 'crashes', () => {
 		it( 'should be an array of caught errors by the watchdog', () => {
-			const watchdog = Watchdog.for( ClassicTestEditor );
+			const watchdog = EditorWatchdog.for( ClassicTestEditor );
 
 			// sinon.stub( window, 'onerror' ).value( undefined ); and similar do not work.
 			const originalErrorHandler = window.onerror;
@@ -919,7 +915,7 @@ describe( 'Watchdog', () => {
 					return;
 				}
 
-				const watchdog = Watchdog.for( ClassicTestEditor );
+				const watchdog = EditorWatchdog.for( ClassicTestEditor );
 
 				// sinon.stub( window, 'onerror' ).value( undefined ); and similar do not work.
 				const originalErrorHandler = window.onerror;
@@ -960,7 +956,7 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'should reflect the state of the watchdog', () => {
-			const watchdog = Watchdog.for( ClassicTestEditor );
+			const watchdog = EditorWatchdog.for( ClassicTestEditor );
 
 			// sinon.stub( window, 'onerror' ).value( undefined ); and similar do not work.
 			const originalErrorHandler = window.onerror;
@@ -994,7 +990,7 @@ describe( 'Watchdog', () => {
 		} );
 
 		it( 'should be observable', () => {
-			const watchdog = Watchdog.for( ClassicTestEditor );
+			const watchdog = EditorWatchdog.for( ClassicTestEditor );
 			const states = [];
 
 			watchdog.on( 'change:state', ( evt, propName, newValue ) => {
@@ -1071,7 +1067,7 @@ describe( 'Watchdog', () => {
 				}
 			}
 
-			const watchdog = Watchdog.for( MultiRootEditor );
+			const watchdog = EditorWatchdog.for( MultiRootEditor );
 
 			// sinon.stub( window, 'onerror' ).value( undefined ); and similar do not work.
 			const originalErrorHandler = window.onerror;

+ 2 - 2
packages/ckeditor5-watchdog/tests/manual/watchdog.js

@@ -8,7 +8,7 @@
 import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
 
-import Watchdog from '../../src/watchdog';
+import EditorWatchdog from '../../src/editorwatchdog';
 
 class TypingError {
 	constructor( editor ) {
@@ -63,7 +63,7 @@ document.getElementById( 'random-error' ).addEventListener( 'click', () => {
 } );
 
 function createWatchdog( editorElement, stateElement, name ) {
-	const watchdog = Watchdog.for( ClassicEditor );
+	const watchdog = EditorWatchdog.for( ClassicEditor );
 
 	watchdog.create( editorElement, editorConfig );