@ckeditor/ckeditor5-watchdog 36.0.1 → 37.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,567 +2,409 @@
2
2
  * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
- /**
7
- * @module watchdog/contextwatchdog
8
- */
9
-
10
- /* globals console */
11
-
12
5
  import Watchdog from './watchdog';
13
6
  import EditorWatchdog from './editorwatchdog';
14
7
  import areConnectedThroughProperties from './utils/areconnectedthroughproperties';
15
8
  import getSubNodes from './utils/getsubnodes';
16
-
17
- const mainQueueId = Symbol( 'MainQueueId' );
18
-
9
+ const mainQueueId = Symbol('MainQueueId');
19
10
  /**
20
11
  * A watchdog for the {@link module:core/context~Context} class.
21
12
  *
22
13
  * See the {@glink features/watchdog Watchdog feature guide} to learn the rationale behind it and
23
14
  * how to use it.
24
- *
25
- * @extends {module:watchdog/watchdog~Watchdog}
26
15
  */
27
16
  export default class ContextWatchdog extends Watchdog {
28
- /**
29
- * The context watchdog class constructor.
30
- *
31
- * const watchdog = new ContextWatchdog( Context );
32
- *
33
- * await watchdog.create( contextConfiguration );
34
- *
35
- * await watchdog.add( item );
36
- *
37
- * See the {@glink features/watchdog Watchdog feature guide} to learn more how to use this feature.
38
- *
39
- * @param {Function} Context The {@link module:core/context~Context} class.
40
- * @param {module:watchdog/watchdog~WatchdogConfig} [watchdogConfig] The watchdog configuration.
41
- */
42
- constructor( Context, watchdogConfig = {} ) {
43
- super( watchdogConfig );
44
-
45
- /**
46
- * A map of internal watchdogs for added items.
47
- *
48
- * @protected
49
- * @type {Map.<string,module:watchdog/watchdog~EditorWatchdog>}
50
- */
51
- this._watchdogs = new Map();
52
-
53
- /**
54
- * The watchdog configuration.
55
- *
56
- * @private
57
- * @type {module:watchdog/watchdog~WatchdogConfig}
58
- */
59
- this._watchdogConfig = watchdogConfig;
60
-
61
- /**
62
- * The current context instance.
63
- *
64
- * @private
65
- * @type {module:core/context~Context|null}
66
- */
67
- this._context = null;
68
-
69
- /**
70
- * Context properties (nodes/references) that are gathered during the initial context creation
71
- * and are used to distinguish the origin of an error.
72
- *
73
- * @private
74
- * @type {Set.<*>}
75
- */
76
- this._contextProps = new Set();
77
-
78
- /**
79
- * An action queue, which is used to handle async functions queuing.
80
- *
81
- * @private
82
- * @type {ActionQueues}
83
- */
84
- this._actionQueues = new ActionQueues();
85
-
86
- /**
87
- * The configuration for the {@link module:core/context~Context}.
88
- *
89
- * @private
90
- * @member {Object} #_contextConfig
91
- */
92
-
93
- /**
94
- * The context configuration.
95
- *
96
- * @private
97
- * @member {Object|undefined} #_config
98
- */
99
-
100
- // Default creator and destructor.
101
- this._creator = contextConfig => Context.create( contextConfig );
102
- this._destructor = context => context.destroy();
103
-
104
- this._actionQueues.onEmpty( () => {
105
- if ( this.state === 'initializing' ) {
106
- this.state = 'ready';
107
- this._fire( 'stateChange' );
108
- }
109
- } );
110
-
111
- /**
112
- * Sets the function that is responsible for the context creation.
113
- * It expects a function that should return a promise (or `undefined`).
114
- *
115
- * watchdog.setCreator( config => Context.create( config ) );
116
- *
117
- * @method #setCreator
118
- * @param {Function} creator
119
- */
120
-
121
- /**
122
- * Sets the function that is responsible for the context destruction.
123
- * Overrides the default destruction function, which destroys only the context instance.
124
- * It expects a function that should return a promise (or `undefined`).
125
- *
126
- * watchdog.setDestructor( context => {
127
- * // Do something before the context is destroyed.
128
- *
129
- * return context
130
- * .destroy()
131
- * .then( () => {
132
- * // Do something after the context is destroyed.
133
- * } );
134
- * } );
135
- *
136
- * @method #setDestructor
137
- * @param {Function} destructor
138
- */
139
- }
140
-
141
- /**
142
- * The context instance. Keep in mind that this property might be changed when the context watchdog restarts,
143
- * so do not keep this instance internally. Always operate on the `ContextWatchdog#context` property.
144
- *
145
- * @type {module:core/context~Context|null}
146
- */
147
- get context() {
148
- return this._context;
149
- }
150
-
151
- /**
152
- * Initializes the context watchdog. Once it is created, the watchdog takes care about
153
- * recreating the context and the provided items, and starts the error handling mechanism.
154
- *
155
- * await watchdog.create( {
156
- * plugins: []
157
- * } );
158
- *
159
- * @param {Object} [contextConfig] The context configuration. See {@link module:core/context~Context}.
160
- * @returns {Promise}
161
- */
162
- create( contextConfig = {} ) {
163
- return this._actionQueues.enqueue( mainQueueId, () => {
164
- this._contextConfig = contextConfig;
165
-
166
- return this._create();
167
- } );
168
- }
169
-
170
- /**
171
- * Returns an item instance with the given `itemId`.
172
- *
173
- * const editor1 = watchdog.getItem( 'editor1' );
174
- *
175
- * @param {String} itemId The item ID.
176
- * @returns {*} The item instance or `undefined` if an item with a given ID has not been found.
177
- */
178
- getItem( itemId ) {
179
- const watchdog = this._getWatchdog( itemId );
180
-
181
- return watchdog._item;
182
- }
183
-
184
- /**
185
- * Gets the state of the given item. See {@link #state} for a list of available states.
186
- *
187
- * const editor1State = watchdog.getItemState( 'editor1' );
188
- *
189
- * @param {String} itemId Item ID.
190
- * @returns {'initializing'|'ready'|'crashed'|'crashedPermanently'|'destroyed'} The state of the item.
191
- */
192
- getItemState( itemId ) {
193
- const watchdog = this._getWatchdog( itemId );
194
-
195
- return watchdog.state;
196
- }
197
-
198
- /**
199
- * Adds items to the watchdog. Once created, instances of these items will be available using the {@link #getItem} method.
200
- *
201
- * Items can be passed together as an array of objects:
202
- *
203
- * await watchdog.add( [ {
204
- * id: 'editor1',
205
- * type: 'editor',
206
- * sourceElementOrData: document.querySelector( '#editor' ),
207
- * config: {
208
- * plugins: [ Essentials, Paragraph, Bold, Italic ],
209
- * toolbar: [ 'bold', 'italic', 'alignment' ]
210
- * },
211
- * creator: ( element, config ) => ClassicEditor.create( element, config )
212
- * } ] );
213
- *
214
- * Or one by one as objects:
215
- *
216
- * await watchdog.add( {
217
- * id: 'editor1',
218
- * type: 'editor',
219
- * sourceElementOrData: document.querySelector( '#editor' ),
220
- * config: {
221
- * plugins: [ Essentials, Paragraph, Bold, Italic ],
222
- * toolbar: [ 'bold', 'italic', 'alignment' ]
223
- * },
224
- * creator: ( element, config ) => ClassicEditor.create( element, config )
225
- * ] );
226
- *
227
- * Then an instance can be retrieved using the {@link #getItem} method:
228
- *
229
- * const editor1 = watchdog.getItem( 'editor1' );
230
- *
231
- * Note that this method can be called multiple times, but for performance reasons it is better
232
- * to pass all items together.
233
- *
234
- * @param {module:watchdog/contextwatchdog~WatchdogItemConfiguration|Array.<module:watchdog/contextwatchdog~WatchdogItemConfiguration>}
235
- * itemConfigurationOrItemConfigurations An item configuration object or an array of item configurations.
236
- * @returns {Promise}
237
- */
238
- add( itemConfigurationOrItemConfigurations ) {
239
- const itemConfigurations = toArray( itemConfigurationOrItemConfigurations );
240
-
241
- return Promise.all( itemConfigurations.map( item => {
242
- return this._actionQueues.enqueue( item.id, () => {
243
- if ( this.state === 'destroyed' ) {
244
- throw new Error( 'Cannot add items to destroyed watchdog.' );
245
- }
246
-
247
- if ( !this._context ) {
248
- throw new Error( 'Context was not created yet. You should call the `ContextWatchdog#create()` method first.' );
249
- }
250
-
251
- let watchdog;
252
-
253
- if ( this._watchdogs.has( item.id ) ) {
254
- throw new Error( `Item with the given id is already added: '${ item.id }'.` );
255
- }
256
-
257
- if ( item.type === 'editor' ) {
258
- watchdog = new EditorWatchdog( this._watchdogConfig );
259
- watchdog.setCreator( item.creator );
260
- watchdog._setExcludedProperties( this._contextProps );
261
-
262
- if ( item.destructor ) {
263
- watchdog.setDestructor( item.destructor );
264
- }
265
-
266
- this._watchdogs.set( item.id, watchdog );
267
-
268
- // Enqueue the internal watchdog errors within the main queue.
269
- // And propagate the internal `error` events as `itemError` event.
270
- watchdog.on( 'error', ( evt, { error, causesRestart } ) => {
271
- this._fire( 'itemError', { itemId: item.id, error } );
272
-
273
- // Do not enqueue the item restart action if the item will not restart.
274
- if ( !causesRestart ) {
275
- return;
276
- }
277
-
278
- this._actionQueues.enqueue( item.id, () => new Promise( res => {
279
- watchdog.on( 'restart', rethrowRestartEventOnce.bind( this ) );
280
-
281
- function rethrowRestartEventOnce() {
282
- watchdog.off( 'restart', rethrowRestartEventOnce );
283
-
284
- this._fire( 'itemRestart', { itemId: item.id } );
285
-
286
- res();
287
- }
288
- } ) );
289
- } );
290
-
291
- return watchdog.create( item.sourceElementOrData, item.config, this._context );
292
- } else {
293
- throw new Error( `Not supported item type: '${ item.type }'.` );
294
- }
295
- } );
296
- } ) );
297
- }
298
-
299
- /**
300
- * Removes and destroys item(s) with given ID(s).
301
- *
302
- * await watchdog.remove( 'editor1' );
303
- *
304
- * Or
305
- *
306
- * await watchdog.remove( [ 'editor1', 'editor2' ] );
307
- *
308
- * @param {Array.<String>|String} itemIdOrItemIds Item ID or an array of item IDs.
309
- * @returns {Promise}
310
- */
311
- remove( itemIdOrItemIds ) {
312
- const itemIds = toArray( itemIdOrItemIds );
313
-
314
- return Promise.all( itemIds.map( itemId => {
315
- return this._actionQueues.enqueue( itemId, () => {
316
- const watchdog = this._getWatchdog( itemId );
317
-
318
- this._watchdogs.delete( itemId );
319
-
320
- return watchdog.destroy();
321
- } );
322
- } ) );
323
- }
324
-
325
- /**
326
- * Destroys the context watchdog and all added items.
327
- * Once the context watchdog is destroyed, new items cannot be added.
328
- *
329
- * await watchdog.destroy();
330
- *
331
- * @returns {Promise}
332
- */
333
- destroy() {
334
- return this._actionQueues.enqueue( mainQueueId, () => {
335
- this.state = 'destroyed';
336
- this._fire( 'stateChange' );
337
-
338
- super.destroy();
339
-
340
- return this._destroy();
341
- } );
342
- }
343
-
344
- /**
345
- * Restarts the context watchdog.
346
- *
347
- * @protected
348
- * @returns {Promise}
349
- */
350
- _restart() {
351
- return this._actionQueues.enqueue( mainQueueId, () => {
352
- this.state = 'initializing';
353
- this._fire( 'stateChange' );
354
-
355
- return this._destroy()
356
- .catch( err => {
357
- console.error( 'An error happened during destroying the context or items.', err );
358
- } )
359
- .then( () => this._create() )
360
- .then( () => this._fire( 'restart' ) );
361
- } );
362
- }
363
-
364
- /**
365
- * @private
366
- * @returns {Promise}
367
- */
368
- _create() {
369
- return Promise.resolve()
370
- .then( () => {
371
- this._startErrorHandling();
372
-
373
- return this._creator( this._contextConfig );
374
- } )
375
- .then( context => {
376
- this._context = context;
377
- this._contextProps = getSubNodes( this._context );
378
-
379
- return Promise.all(
380
- Array.from( this._watchdogs.values() )
381
- .map( watchdog => {
382
- watchdog._setExcludedProperties( this._contextProps );
383
-
384
- return watchdog.create( undefined, undefined, this._context );
385
- } )
386
- );
387
- } );
388
- }
389
-
390
- /**
391
- * Destroys the context instance and all added items.
392
- *
393
- * @private
394
- * @returns {Promise}
395
- */
396
- _destroy() {
397
- return Promise.resolve()
398
- .then( () => {
399
- this._stopErrorHandling();
400
-
401
- const context = this._context;
402
-
403
- this._context = null;
404
- this._contextProps = new Set();
405
-
406
- return Promise.all(
407
- Array.from( this._watchdogs.values() )
408
- .map( watchdog => watchdog.destroy() )
409
- )
410
- // Context destructor destroys each editor.
411
- .then( () => this._destructor( context ) );
412
- } );
413
- }
414
-
415
- /**
416
- * Returns the watchdog for a given item ID.
417
- *
418
- * @protected
419
- * @param {String} itemId Item ID.
420
- * @returns {module:watchdog/watchdog~Watchdog} Watchdog
421
- */
422
- _getWatchdog( itemId ) {
423
- const watchdog = this._watchdogs.get( itemId );
424
-
425
- if ( !watchdog ) {
426
- throw new Error( `Item with the given id was not registered: ${ itemId }.` );
427
- }
428
-
429
- return watchdog;
430
- }
431
-
432
- /**
433
- * Checks whether an error comes from the context instance and not from the item instances.
434
- *
435
- * @protected
436
- * @param {Error} error
437
- * @returns {Boolean}
438
- */
439
- _isErrorComingFromThisItem( error ) {
440
- for ( const watchdog of this._watchdogs.values() ) {
441
- if ( watchdog._isErrorComingFromThisItem( error ) ) {
442
- return false;
443
- }
444
- }
445
-
446
- return areConnectedThroughProperties( this._context, error.context );
447
- }
448
-
449
- /**
450
- * Fired after the watchdog restarts the context and the added items because of a crash.
451
- *
452
- * watchdog.on( 'restart', () => {
453
- * console.log( 'The context has been restarted.' );
454
- * } );
455
- *
456
- * @event restart
457
- */
458
-
459
- /**
460
- * Fired when a new error occurred in one of the added items.
461
- *
462
- * watchdog.on( 'itemError', ( evt, { error, itemId, causesRestart } ) => {
463
- * console.log( `An error occurred in an item with the '${ itemId }' ID.` );
464
- * } );
465
- *
466
- * @event itemError
467
- */
468
-
469
- /**
470
- * Fired after an item has been restarted.
471
- *
472
- * watchdog.on( 'itemRestart', ( evt, { itemId } ) => {
473
- * console.log( 'An item with with the '${ itemId }' ID has been restarted.' );
474
- * } );
475
- *
476
- * @event itemRestart
477
- */
17
+ /**
18
+ * The context watchdog class constructor.
19
+ *
20
+ * ```ts
21
+ * const watchdog = new ContextWatchdog( Context );
22
+ *
23
+ * await watchdog.create( contextConfiguration );
24
+ *
25
+ * await watchdog.add( item );
26
+ * ```
27
+ *
28
+ * See the {@glink features/watchdog Watchdog feature guide} to learn more how to use this feature.
29
+ *
30
+ * @param Context The {@link module:core/context~Context} class.
31
+ * @param watchdogConfig The watchdog configuration.
32
+ */
33
+ constructor(Context, watchdogConfig = {}) {
34
+ super(watchdogConfig);
35
+ /**
36
+ * A map of internal watchdogs for added items.
37
+ */
38
+ this._watchdogs = new Map();
39
+ /**
40
+ * The current context instance.
41
+ */
42
+ this._context = null;
43
+ /**
44
+ * Context properties (nodes/references) that are gathered during the initial context creation
45
+ * and are used to distinguish the origin of an error.
46
+ */
47
+ this._contextProps = new Set();
48
+ /**
49
+ * An action queue, which is used to handle async functions queuing.
50
+ */
51
+ this._actionQueues = new ActionQueues();
52
+ this._watchdogConfig = watchdogConfig;
53
+ // Default creator and destructor.
54
+ this._creator = contextConfig => Context.create(contextConfig);
55
+ this._destructor = context => context.destroy();
56
+ this._actionQueues.onEmpty(() => {
57
+ if (this.state === 'initializing') {
58
+ this.state = 'ready';
59
+ this._fire('stateChange');
60
+ }
61
+ });
62
+ }
63
+ /**
64
+ * Sets the function that is responsible for the context creation.
65
+ * It expects a function that should return a promise (or `undefined`).
66
+ *
67
+ * ```ts
68
+ * watchdog.setCreator( config => Context.create( config ) );
69
+ * ```
70
+ */
71
+ setCreator(creator) {
72
+ this._creator = creator;
73
+ }
74
+ /**
75
+ * Sets the function that is responsible for the context destruction.
76
+ * Overrides the default destruction function, which destroys only the context instance.
77
+ * It expects a function that should return a promise (or `undefined`).
78
+ *
79
+ * ```ts
80
+ * watchdog.setDestructor( context => {
81
+ * // Do something before the context is destroyed.
82
+ *
83
+ * return context
84
+ * .destroy()
85
+ * .then( () => {
86
+ * // Do something after the context is destroyed.
87
+ * } );
88
+ * } );
89
+ * ```
90
+ */
91
+ setDestructor(destructor) {
92
+ this._destructor = destructor;
93
+ }
94
+ /**
95
+ * The context instance. Keep in mind that this property might be changed when the context watchdog restarts,
96
+ * so do not keep this instance internally. Always operate on the `ContextWatchdog#context` property.
97
+ */
98
+ get context() {
99
+ return this._context;
100
+ }
101
+ /**
102
+ * Initializes the context watchdog. Once it is created, the watchdog takes care about
103
+ * recreating the context and the provided items, and starts the error handling mechanism.
104
+ *
105
+ * ```ts
106
+ * await watchdog.create( {
107
+ * plugins: []
108
+ * } );
109
+ * ```
110
+ *
111
+ * @param contextConfig The context configuration. See {@link module:core/context~Context}.
112
+ */
113
+ create(contextConfig = {}) {
114
+ return this._actionQueues.enqueue(mainQueueId, () => {
115
+ this._contextConfig = contextConfig;
116
+ return this._create();
117
+ });
118
+ }
119
+ /**
120
+ * Returns an item instance with the given `itemId`.
121
+ *
122
+ * ```ts
123
+ * const editor1 = watchdog.getItem( 'editor1' );
124
+ * ```
125
+ *
126
+ * @param itemId The item ID.
127
+ * @returns The item instance or `undefined` if an item with a given ID has not been found.
128
+ */
129
+ getItem(itemId) {
130
+ const watchdog = this._getWatchdog(itemId);
131
+ return watchdog._item;
132
+ }
133
+ /**
134
+ * Gets the state of the given item. See {@link #state} for a list of available states.
135
+ *
136
+ * ```ts
137
+ * const editor1State = watchdog.getItemState( 'editor1' );
138
+ * ```
139
+ *
140
+ * @param itemId Item ID.
141
+ * @returns The state of the item.
142
+ */
143
+ getItemState(itemId) {
144
+ const watchdog = this._getWatchdog(itemId);
145
+ return watchdog.state;
146
+ }
147
+ /**
148
+ * Adds items to the watchdog. Once created, instances of these items will be available using the {@link #getItem} method.
149
+ *
150
+ * Items can be passed together as an array of objects:
151
+ *
152
+ * ```ts
153
+ * await watchdog.add( [ {
154
+ * id: 'editor1',
155
+ * type: 'editor',
156
+ * sourceElementOrData: document.querySelector( '#editor' ),
157
+ * config: {
158
+ * plugins: [ Essentials, Paragraph, Bold, Italic ],
159
+ * toolbar: [ 'bold', 'italic', 'alignment' ]
160
+ * },
161
+ * creator: ( element, config ) => ClassicEditor.create( element, config )
162
+ * } ] );
163
+ * ```
164
+ *
165
+ * Or one by one as objects:
166
+ *
167
+ * ```ts
168
+ * await watchdog.add( {
169
+ * id: 'editor1',
170
+ * type: 'editor',
171
+ * sourceElementOrData: document.querySelector( '#editor' ),
172
+ * config: {
173
+ * plugins: [ Essentials, Paragraph, Bold, Italic ],
174
+ * toolbar: [ 'bold', 'italic', 'alignment' ]
175
+ * },
176
+ * creator: ( element, config ) => ClassicEditor.create( element, config )
177
+ * ] );
178
+ * ```
179
+ *
180
+ * Then an instance can be retrieved using the {@link #getItem} method:
181
+ *
182
+ * ```ts
183
+ * const editor1 = watchdog.getItem( 'editor1' );
184
+ * ```
185
+ *
186
+ * Note that this method can be called multiple times, but for performance reasons it is better
187
+ * to pass all items together.
188
+ *
189
+ * @param itemConfigurationOrItemConfigurations An item configuration object or an array of item configurations.
190
+ */
191
+ add(itemConfigurationOrItemConfigurations) {
192
+ const itemConfigurations = toArray(itemConfigurationOrItemConfigurations);
193
+ return Promise.all(itemConfigurations.map(item => {
194
+ return this._actionQueues.enqueue(item.id, () => {
195
+ if (this.state === 'destroyed') {
196
+ throw new Error('Cannot add items to destroyed watchdog.');
197
+ }
198
+ if (!this._context) {
199
+ throw new Error('Context was not created yet. You should call the `ContextWatchdog#create()` method first.');
200
+ }
201
+ let watchdog;
202
+ if (this._watchdogs.has(item.id)) {
203
+ throw new Error(`Item with the given id is already added: '${item.id}'.`);
204
+ }
205
+ if (item.type === 'editor') {
206
+ watchdog = new EditorWatchdog(null, this._watchdogConfig);
207
+ watchdog.setCreator(item.creator);
208
+ watchdog._setExcludedProperties(this._contextProps);
209
+ if (item.destructor) {
210
+ watchdog.setDestructor(item.destructor);
211
+ }
212
+ this._watchdogs.set(item.id, watchdog);
213
+ // Enqueue the internal watchdog errors within the main queue.
214
+ // And propagate the internal `error` events as `itemError` event.
215
+ watchdog.on('error', (evt, { error, causesRestart }) => {
216
+ this._fire('itemError', { itemId: item.id, error });
217
+ // Do not enqueue the item restart action if the item will not restart.
218
+ if (!causesRestart) {
219
+ return;
220
+ }
221
+ this._actionQueues.enqueue(item.id, () => new Promise(res => {
222
+ const rethrowRestartEventOnce = () => {
223
+ watchdog.off('restart', rethrowRestartEventOnce);
224
+ this._fire('itemRestart', { itemId: item.id });
225
+ res();
226
+ };
227
+ watchdog.on('restart', rethrowRestartEventOnce);
228
+ }));
229
+ });
230
+ return watchdog.create(item.sourceElementOrData, item.config, this._context);
231
+ }
232
+ else {
233
+ throw new Error(`Not supported item type: '${item.type}'.`);
234
+ }
235
+ });
236
+ }));
237
+ }
238
+ /**
239
+ * Removes and destroys item(s) with given ID(s).
240
+ *
241
+ * ```ts
242
+ * await watchdog.remove( 'editor1' );
243
+ * ```
244
+ *
245
+ * Or
246
+ *
247
+ * ```ts
248
+ * await watchdog.remove( [ 'editor1', 'editor2' ] );
249
+ * ```
250
+ *
251
+ * @param itemIdOrItemIds Item ID or an array of item IDs.
252
+ */
253
+ remove(itemIdOrItemIds) {
254
+ const itemIds = toArray(itemIdOrItemIds);
255
+ return Promise.all(itemIds.map(itemId => {
256
+ return this._actionQueues.enqueue(itemId, () => {
257
+ const watchdog = this._getWatchdog(itemId);
258
+ this._watchdogs.delete(itemId);
259
+ return watchdog.destroy();
260
+ });
261
+ }));
262
+ }
263
+ /**
264
+ * Destroys the context watchdog and all added items.
265
+ * Once the context watchdog is destroyed, new items cannot be added.
266
+ *
267
+ * ```ts
268
+ * await watchdog.destroy();
269
+ * ```
270
+ */
271
+ destroy() {
272
+ return this._actionQueues.enqueue(mainQueueId, () => {
273
+ this.state = 'destroyed';
274
+ this._fire('stateChange');
275
+ super.destroy();
276
+ return this._destroy();
277
+ });
278
+ }
279
+ /**
280
+ * Restarts the context watchdog.
281
+ */
282
+ _restart() {
283
+ return this._actionQueues.enqueue(mainQueueId, () => {
284
+ this.state = 'initializing';
285
+ this._fire('stateChange');
286
+ return this._destroy()
287
+ .catch(err => {
288
+ console.error('An error happened during destroying the context or items.', err);
289
+ })
290
+ .then(() => this._create())
291
+ .then(() => this._fire('restart'));
292
+ });
293
+ }
294
+ /**
295
+ * Initializes the context watchdog.
296
+ */
297
+ _create() {
298
+ return Promise.resolve()
299
+ .then(() => {
300
+ this._startErrorHandling();
301
+ return this._creator(this._contextConfig);
302
+ })
303
+ .then(context => {
304
+ this._context = context;
305
+ this._contextProps = getSubNodes(this._context);
306
+ return Promise.all(Array.from(this._watchdogs.values())
307
+ .map(watchdog => {
308
+ watchdog._setExcludedProperties(this._contextProps);
309
+ return watchdog.create(undefined, undefined, this._context);
310
+ }));
311
+ });
312
+ }
313
+ /**
314
+ * Destroys the context instance and all added items.
315
+ */
316
+ _destroy() {
317
+ return Promise.resolve()
318
+ .then(() => {
319
+ this._stopErrorHandling();
320
+ const context = this._context;
321
+ this._context = null;
322
+ this._contextProps = new Set();
323
+ return Promise.all(Array.from(this._watchdogs.values())
324
+ .map(watchdog => watchdog.destroy()))
325
+ // Context destructor destroys each editor.
326
+ .then(() => this._destructor(context));
327
+ });
328
+ }
329
+ /**
330
+ * Returns the watchdog for a given item ID.
331
+ *
332
+ * @param itemId Item ID.
333
+ */
334
+ _getWatchdog(itemId) {
335
+ const watchdog = this._watchdogs.get(itemId);
336
+ if (!watchdog) {
337
+ throw new Error(`Item with the given id was not registered: ${itemId}.`);
338
+ }
339
+ return watchdog;
340
+ }
341
+ /**
342
+ * Checks whether an error comes from the context instance and not from the item instances.
343
+ *
344
+ * @internal
345
+ */
346
+ _isErrorComingFromThisItem(error) {
347
+ for (const watchdog of this._watchdogs.values()) {
348
+ if (watchdog._isErrorComingFromThisItem(error)) {
349
+ return false;
350
+ }
351
+ }
352
+ return areConnectedThroughProperties(this._context, error.context);
353
+ }
478
354
  }
479
-
480
- // Manager of action queues that allows queuing async functions.
355
+ /**
356
+ * Manager of action queues that allows queuing async functions.
357
+ */
481
358
  class ActionQueues {
482
- constructor() {
483
- // @type {Array.<Function>}
484
- this._onEmptyCallbacks = [];
485
-
486
- // @type {Map.<Promise>}
487
- this._queues = new Map();
488
-
489
- this._actions = new WeakMap();
490
-
491
- this._lastActionId = 0;
492
-
493
- this._activeActions = 0;
494
- }
495
-
496
- // Used to register callbacks that will be run when the queue becomes empty.
497
- //
498
- // @param {Function} onEmptyCallback A callback that will be run whenever the queue becomes empty.
499
- onEmpty( onEmptyCallback ) {
500
- this._onEmptyCallbacks.push( onEmptyCallback );
501
- }
502
-
503
- // It adds asynchronous actions (functions) to the proper queue and runs them one by one.
504
- //
505
- // @param {Symbol|String|Number} queueId The action queue ID.
506
- // @param {Function} action A function that should be enqueued.
507
- // @returns {Promise}
508
- enqueue( queueId, action ) {
509
- const isMainAction = queueId === mainQueueId;
510
-
511
- this._activeActions++;
512
-
513
- if ( !this._queues.get( queueId ) ) {
514
- this._queues.set( queueId, Promise.resolve() );
515
- }
516
-
517
- // List all sources of actions that the current action needs to await for.
518
- // For the main action wait for all other actions.
519
- // For the item action wait only for the item queue and the main queue.
520
- const awaitedActions = isMainAction ?
521
- Promise.all( this._queues.values() ) :
522
- Promise.all( [ this._queues.get( mainQueueId ), this._queues.get( queueId ) ] );
523
-
524
- const queueWithAction = awaitedActions.then( action );
525
-
526
- // Catch all errors in the main queue to stack promises even if an error occurred in the past.
527
- const nonErrorQueue = queueWithAction.catch( () => {} );
528
-
529
- this._queues.set( queueId, nonErrorQueue );
530
-
531
- return queueWithAction.finally( () => {
532
- this._activeActions--;
533
-
534
- if ( this._queues.get( queueId ) === nonErrorQueue && this._activeActions === 0 ) {
535
- this._onEmptyCallbacks.forEach( cb => cb() );
536
- }
537
- } );
538
- }
539
- }
540
-
541
- // Transforms any value to an array. If the provided value is already an array, it is returned unchanged.
542
- //
543
- // @param {*} elementOrArray The value to transform to an array.
544
- // @returns {Array} An array created from data.
545
- function toArray( elementOrArray ) {
546
- return Array.isArray( elementOrArray ) ? elementOrArray : [ elementOrArray ];
359
+ constructor() {
360
+ this._onEmptyCallbacks = [];
361
+ this._queues = new Map();
362
+ this._activeActions = 0;
363
+ }
364
+ /**
365
+ * Used to register callbacks that will be run when the queue becomes empty.
366
+ *
367
+ * @param onEmptyCallback A callback that will be run whenever the queue becomes empty.
368
+ */
369
+ onEmpty(onEmptyCallback) {
370
+ this._onEmptyCallbacks.push(onEmptyCallback);
371
+ }
372
+ /**
373
+ * It adds asynchronous actions (functions) to the proper queue and runs them one by one.
374
+ *
375
+ * @param queueId The action queue ID.
376
+ * @param action A function that should be enqueued.
377
+ */
378
+ enqueue(queueId, action) {
379
+ const isMainAction = queueId === mainQueueId;
380
+ this._activeActions++;
381
+ if (!this._queues.get(queueId)) {
382
+ this._queues.set(queueId, Promise.resolve());
383
+ }
384
+ // List all sources of actions that the current action needs to await for.
385
+ // For the main action wait for all other actions.
386
+ // For the item action wait only for the item queue and the main queue.
387
+ const awaitedActions = isMainAction ?
388
+ Promise.all(this._queues.values()) :
389
+ Promise.all([this._queues.get(mainQueueId), this._queues.get(queueId)]);
390
+ const queueWithAction = awaitedActions.then(action);
391
+ // Catch all errors in the main queue to stack promises even if an error occurred in the past.
392
+ const nonErrorQueue = queueWithAction.catch(() => { });
393
+ this._queues.set(queueId, nonErrorQueue);
394
+ return queueWithAction.finally(() => {
395
+ this._activeActions--;
396
+ if (this._queues.get(queueId) === nonErrorQueue && this._activeActions === 0) {
397
+ this._onEmptyCallbacks.forEach(cb => cb());
398
+ }
399
+ });
400
+ }
547
401
  }
548
-
549
402
  /**
550
- * The watchdog item configuration interface.
551
- *
552
- * @typedef {Object} module:watchdog/contextwatchdog~WatchdogItemConfiguration
403
+ * Transforms any value to an array. If the provided value is already an array, it is returned unchanged.
553
404
  *
554
- * @property {String} id A unique item identificator.
555
- *
556
- * @property {'editor'} type The type of the item to create. At the moment, only `'editor'` is supported.
557
- *
558
- * @property {Function} creator A function that initializes the item (the editor). The function takes editor initialization arguments
559
- * and should return a promise. For example: `( el, config ) => ClassicEditor.create( el, config )`.
560
- *
561
- * @property {Function} [destructor] A function that destroys the item instance (the editor). The function
562
- * takes an item and should return a promise. For example: `editor => editor.destroy()`
563
- *
564
- * @property {String|HTMLElement} sourceElementOrData The source element or data that will be passed
565
- * as the first argument to the `Editor.create()` method.
566
- *
567
- * @property {Object} config An editor configuration.
405
+ * @param elementOrArray The value to transform to an array.
406
+ * @returns An array created from data.
568
407
  */
408
+ function toArray(elementOrArray) {
409
+ return Array.isArray(elementOrArray) ? elementOrArray : [elementOrArray];
410
+ }