@ckeditor/ckeditor5-engine 39.0.2 → 40.1.0

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.
package/LICENSE.md CHANGED
@@ -2,7 +2,7 @@ Software License Agreement
2
2
  ==========================
3
3
 
4
4
  **CKEditor&nbsp;5 editing engine** – https://github.com/ckeditor/ckeditor5-engine <br>
5
- Copyright (c) 2003-2023, [CKSource Holding sp. z o.o.](https://cksource.com) All rights reserved.
5
+ Copyright (c) 20032023, [CKSource Holding sp. z o.o.](https://cksource.com) All rights reserved.
6
6
 
7
7
  Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
8
8
 
@@ -13,9 +13,9 @@ Where not otherwise indicated, all CKEditor content is authored by CKSource engi
13
13
 
14
14
  The following libraries are included in CKEditor under the [MIT license](https://opensource.org/licenses/MIT):
15
15
 
16
- * lodash - Copyright (c) JS Foundation and other contributors https://js.foundation/. Based on Underscore.js, copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors http://underscorejs.org/.
16
+ * Lodash - Copyright (c) JS Foundation and other contributors https://js.foundation/. Based on Underscore.js, copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors http://underscorejs.org/.
17
17
 
18
18
  Trademarks
19
19
  ----------
20
20
 
21
- **CKEditor** is a trademark of [CKSource Holding sp. z o.o.](https://cksource.com) All other brand and product names are trademarks, registered trademarks or service marks of their respective holders.
21
+ **CKEditor** is a trademark of [CKSource Holding sp. z o.o.](https://cksource.com) All other brand and product names are trademarks, registered trademarks, or service marks of their respective holders.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-engine",
3
- "version": "39.0.2",
3
+ "version": "40.1.0",
4
4
  "description": "The editing engine of CKEditor 5 – the best browser-based rich text editor.",
5
5
  "keywords": [
6
6
  "wysiwyg",
@@ -23,7 +23,7 @@
23
23
  ],
24
24
  "main": "src/index.js",
25
25
  "dependencies": {
26
- "@ckeditor/ckeditor5-utils": "39.0.2",
26
+ "@ckeditor/ckeditor5-utils": "40.1.0",
27
27
  "lodash-es": "4.17.21"
28
28
  },
29
29
  "author": "CKSource (http://cksource.com/)",
@@ -45,7 +45,7 @@ export default class DataController extends DataController_base {
45
45
  readonly model: Model;
46
46
  /**
47
47
  * Mapper used for the conversion. It has no permanent bindings, because these are created while getting data and
48
- * ae cleared directly after the data are converted. However, the mapper is defined as a class property, because
48
+ * are cleared directly after the data are converted. However, the mapper is defined as a class property, because
49
49
  * it needs to be passed to the `DowncastDispatcher` as a conversion API.
50
50
  */
51
51
  readonly mapper: Mapper;
@@ -258,7 +258,7 @@ export default class DowncastDispatcher extends EmitterMixin() {
258
258
  _convertInsert(range, conversionApi, options = {}) {
259
259
  if (!options.doNotAddConsumables) {
260
260
  // Collect a list of things that can be consumed, consisting of nodes and their attributes.
261
- this._addConsumablesForInsert(conversionApi.consumable, Array.from(range));
261
+ this._addConsumablesForInsert(conversionApi.consumable, range);
262
262
  }
263
263
  // Fire a separate insert event for each node and text fragment contained in the range.
264
264
  for (const data of Array.from(range.getWalker({ shallow: true })).map(walkerValueToEventData)) {
@@ -1810,16 +1810,11 @@ function normalizeModelElementConfig(model) {
1810
1810
  if (typeof model == 'string') {
1811
1811
  model = { name: model };
1812
1812
  }
1813
- // List of attributes that should trigger reconversion.
1814
- if (!model.attributes) {
1815
- model.attributes = [];
1816
- }
1817
- else if (!Array.isArray(model.attributes)) {
1818
- model.attributes = [model.attributes];
1819
- }
1820
- // Whether a children insertion/deletion should trigger reconversion.
1821
- model.children = !!model.children;
1822
- return model;
1813
+ return {
1814
+ name: model.name,
1815
+ attributes: model.attributes ? toArray(model.attributes) : [],
1816
+ children: !!model.children
1817
+ };
1823
1818
  }
1824
1819
  /**
1825
1820
  * Takes `config.view`, and if it is an {@link module:engine/view/elementdefinition~ElementDefinition}, converts it
@@ -187,7 +187,7 @@ export default class UpcastDispatcher extends EmitterMixin() {
187
187
  const documentFragment = writer.createDocumentFragment();
188
188
  // When there is a conversion result.
189
189
  if (modelRange) {
190
- // Remove all empty elements that were create while splitting.
190
+ // Remove all empty elements that were created while splitting.
191
191
  this._removeEmptyElements();
192
192
  // Move all items that were converted in context tree to the document fragment.
193
193
  for (const item of Array.from(this._modelCursor.parent.getChildren())) {
@@ -430,11 +430,15 @@ export class ViewElementConsumables {
430
430
  *
431
431
  * What you have done is trying to use:
432
432
  *
433
- * consumables.add( { attributes: [ 'class', 'style' ] } );
433
+ * ```ts
434
+ * consumables.add( { attributes: [ 'class', 'style' ] } );
435
+ * ```
434
436
  *
435
437
  * While each class and style should be registered separately:
436
438
  *
437
- * consumables.add( { classes: 'some-class', styles: 'font-weight' } );
439
+ * ```ts
440
+ * consumables.add( { classes: 'some-class', styles: 'font-weight' } );
441
+ * ```
438
442
  *
439
443
  * @error viewconsumable-invalid-attribute
440
444
  */
@@ -2,6 +2,7 @@
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
+ import { global } from '@ckeditor/ckeditor5-utils';
5
6
  /**
6
7
  * Basic HTML writer. It uses the native `innerHTML` property for basic conversion
7
8
  * from a document fragment to an HTML string.
@@ -11,7 +12,7 @@ export default class BasicHtmlWriter {
11
12
  * Returns an HTML string created from the document fragment.
12
13
  */
13
14
  getHtml(fragment) {
14
- const doc = document.implementation.createHTMLDocument('');
15
+ const doc = global.document.implementation.createHTMLDocument('');
15
16
  const container = doc.createElement('div');
16
17
  container.appendChild(fragment);
17
18
  return container.innerHTML;
package/src/index.d.ts CHANGED
@@ -30,7 +30,7 @@ export { default as RootAttributeOperation } from './model/operation/rootattribu
30
30
  export { default as RootOperation } from './model/operation/rootoperation';
31
31
  export { default as NoOperation } from './model/operation/nooperation';
32
32
  export { transformSets } from './model/operation/transform';
33
- export { default as DocumentSelection, type DocumentSelectionChangeRangeEvent } from './model/documentselection';
33
+ export { default as DocumentSelection, type DocumentSelectionChangeRangeEvent, type DocumentSelectionChangeMarkerEvent } from './model/documentselection';
34
34
  export { default as Range } from './model/range';
35
35
  export { default as LiveRange, type LiveRangeChangeRangeEvent } from './model/liverange';
36
36
  export { default as LivePosition } from './model/liveposition';
@@ -47,7 +47,7 @@ export type { Marker } from './model/markercollection';
47
47
  export type { default as Batch } from './model/batch';
48
48
  export type { default as Differ, DiffItem, DiffItemAttribute, DiffItemInsert, DiffItemRemove } from './model/differ';
49
49
  export type { default as Item } from './model/item';
50
- export type { default as Node } from './model/node';
50
+ export type { default as Node, NodeAttributes } from './model/node';
51
51
  export type { default as RootElement } from './model/rootelement';
52
52
  export type { default as Schema, SchemaAttributeCheckCallback, SchemaChildCheckCallback, AttributeProperties, SchemaItemDefinition } from './model/schema';
53
53
  export type { default as Selection, Selectable } from './model/selection';
@@ -74,7 +74,7 @@ export { default as ViewRawElement } from './view/rawelement';
74
74
  export { default as ViewUIElement } from './view/uielement';
75
75
  export { default as ViewDocumentFragment } from './view/documentfragment';
76
76
  export { default as ViewTreeWalker, type TreeWalkerValue as ViewTreeWalkerValue } from './view/treewalker';
77
- export type { default as ViewElementDefinition } from './view/elementdefinition';
77
+ export type { default as ViewElementDefinition, ElementObjectDefinition } from './view/elementdefinition';
78
78
  export type { default as ViewDocumentSelection } from './view/documentselection';
79
79
  export { default as AttributeElement } from './view/attributeelement';
80
80
  export type { default as ViewItem } from './view/item';
@@ -17,7 +17,7 @@ import type Operation from './operation/operation';
17
17
  * @see module:engine/model/model~Model#enqueueChange
18
18
  * @see module:engine/model/model~Model#change
19
19
  */
20
- export default class Batch {
20
+ export default class Batch implements BatchType {
21
21
  /**
22
22
  * An array of operations that compose this batch.
23
23
  */
@@ -192,8 +192,7 @@ export default class Document extends EmitterMixin() {
192
192
  * @param includeDetached Specified whether detached roots should be returned as well.
193
193
  */
194
194
  getRoots(includeDetached = false) {
195
- return Array.from(this.roots)
196
- .filter(root => root != this.graveyard && (includeDetached || root.isAttached()) && root._isLoaded);
195
+ return this.roots.filter(root => root != this.graveyard && (includeDetached || root.isAttached()) && root._isLoaded);
197
196
  }
198
197
  /**
199
198
  * Used to register a post-fixer callback. A post-fixer mechanism guarantees that the features
@@ -107,10 +107,10 @@ export default class MarkerCollection extends MarkerCollection_base implements I
107
107
  * Iterates over all markers that starts with given `prefix`.
108
108
  *
109
109
  * ```ts
110
- * const markerFooA = markersCollection.set( 'foo:a', rangeFooA );
111
- * const markerFooB = markersCollection.set( 'foo:b', rangeFooB );
112
- * const markerBarA = markersCollection.set( 'bar:a', rangeBarA );
113
- * const markerFooBarA = markersCollection.set( 'foobar:a', rangeFooBarA );
110
+ * const markerFooA = markersCollection._set( 'foo:a', rangeFooA );
111
+ * const markerFooB = markersCollection._set( 'foo:b', rangeFooB );
112
+ * const markerBarA = markersCollection._set( 'bar:a', rangeBarA );
113
+ * const markerFooBarA = markersCollection._set( 'foobar:a', rangeFooBarA );
114
114
  * Array.from( markersCollection.getMarkersGroup( 'foo' ) ); // [ markerFooA, markerFooB ]
115
115
  * Array.from( markersCollection.getMarkersGroup( 'a' ) ); // []
116
116
  * ```
@@ -190,10 +190,10 @@ export default class MarkerCollection extends EmitterMixin() {
190
190
  * Iterates over all markers that starts with given `prefix`.
191
191
  *
192
192
  * ```ts
193
- * const markerFooA = markersCollection.set( 'foo:a', rangeFooA );
194
- * const markerFooB = markersCollection.set( 'foo:b', rangeFooB );
195
- * const markerBarA = markersCollection.set( 'bar:a', rangeBarA );
196
- * const markerFooBarA = markersCollection.set( 'foobar:a', rangeFooBarA );
193
+ * const markerFooA = markersCollection._set( 'foo:a', rangeFooA );
194
+ * const markerFooB = markersCollection._set( 'foo:b', rangeFooB );
195
+ * const markerBarA = markersCollection._set( 'bar:a', rangeBarA );
196
+ * const markerFooBarA = markersCollection._set( 'foobar:a', rangeFooBarA );
197
197
  * Array.from( markersCollection.getMarkersGroup( 'foo' ) ); // [ markerFooA, markerFooB ]
198
198
  * Array.from( markersCollection.getMarkersGroup( 'a' ) ); // []
199
199
  * ```
@@ -2,12 +2,12 @@
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
- import type Batch from '../batch';
6
- import type Document from '../document';
7
- import type { Selectable } from '../selection';
8
5
  /**
9
6
  * @module engine/model/operation/operation
10
7
  */
8
+ import type Batch from '../batch';
9
+ import type Document from '../document';
10
+ import type { Selectable } from '../selection';
11
11
  /**
12
12
  * Abstract base operation class.
13
13
  */
@@ -2,9 +2,6 @@
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
- * @module engine/model/operation/operation
7
- */
8
5
  /**
9
6
  * Abstract base operation class.
10
7
  */
@@ -2,6 +2,9 @@
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
+ * @module engine/model/operation/transform
7
+ */
5
8
  import InsertOperation from './insertoperation';
6
9
  import AttributeOperation from './attributeoperation';
7
10
  import RenameOperation from './renameoperation';
@@ -16,9 +19,6 @@ import Range from '../range';
16
19
  import Position from '../position';
17
20
  import { compareArrays } from '@ckeditor/ckeditor5-utils';
18
21
  const transformations = new Map();
19
- /**
20
- * @module engine/model/operation/transform
21
- */
22
22
  /**
23
23
  * Sets a transformation function to be be used to transform instances of class `OperationA` by instances of class `OperationB`.
24
24
  *
@@ -138,7 +138,11 @@ export function _normalizeNodes(nodes) {
138
138
  convert(node);
139
139
  }
140
140
  }
141
- // Skip unrecognized type.
141
+ else {
142
+ // Skip unrecognized type.
143
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
144
+ const unreachable = nodes;
145
+ }
142
146
  }
143
147
  convert(nodes);
144
148
  // Merge text nodes.
@@ -128,6 +128,8 @@ export default class Document extends Document_base {
128
128
  * // Let other post-fixers know that something changed.
129
129
  * return true;
130
130
  * }
131
+ *
132
+ * return false;
131
133
  * } );
132
134
  * ```
133
135
  *
@@ -166,8 +168,6 @@ export type ViewDocumentPostFixer = (writer: DowncastWriter) => boolean;
166
168
  * * `children` - for child list changes,
167
169
  * * `attributes` - for element attributes changes,
168
170
  * * `text` - for text nodes changes.
169
- *
170
- * @typedef {String} module:engine/view/document~ChangeType
171
171
  */
172
172
  export type ChangeType = 'children' | 'attributes' | 'text';
173
173
  /**
@@ -79,6 +79,8 @@ export default class Document extends BubblingEmitterMixin(ObservableMixin()) {
79
79
  * // Let other post-fixers know that something changed.
80
80
  * return true;
81
81
  * }
82
+ *
83
+ * return false;
82
84
  * } );
83
85
  * ```
84
86
  *
@@ -98,7 +100,7 @@ export default class Document extends BubblingEmitterMixin(ObservableMixin()) {
98
100
  * Destroys this instance. Makes sure that all observers are destroyed and listeners removed.
99
101
  */
100
102
  destroy() {
101
- this.roots.map(root => root.destroy());
103
+ this.roots.forEach(root => root.destroy());
102
104
  this.stopListening();
103
105
  }
104
106
  /**
@@ -65,6 +65,10 @@ export default class DocumentFragment extends DocumentFragment_base implements I
65
65
  * Artificial element name. Returns `undefined`. Added for compatibility reasons.
66
66
  */
67
67
  get name(): undefined;
68
+ /**
69
+ * Artificial element getFillerOffset. Returns `undefined`. Added for compatibility reasons.
70
+ */
71
+ get getFillerOffset(): undefined;
68
72
  /**
69
73
  * Returns the custom property value for the given key.
70
74
  */
@@ -78,6 +78,12 @@ export default class DocumentFragment extends EmitterMixin(TypeCheckable) {
78
78
  get name() {
79
79
  return undefined;
80
80
  }
81
+ /**
82
+ * Artificial element getFillerOffset. Returns `undefined`. Added for compatibility reasons.
83
+ */
84
+ get getFillerOffset() {
85
+ return undefined;
86
+ }
81
87
  /**
82
88
  * Returns the custom property value for the given key.
83
89
  */
@@ -101,6 +101,10 @@ export default class DomConverter {
101
101
  * Matcher for inline object view elements. This is an extension of a simple {@link #inlineObjectElements} array of element names.
102
102
  */
103
103
  private readonly _inlineObjectElementMatcher;
104
+ /**
105
+ * Set of elements with temporary custom properties that require clearing after render.
106
+ */
107
+ private readonly _elementsWithTemporaryCustomProperties;
104
108
  /**
105
109
  * Creates a DOM converter.
106
110
  *
@@ -167,20 +171,22 @@ export default class DomConverter {
167
171
  * @param html Textual representation of the HTML that will be set on `domElement`.
168
172
  */
169
173
  setContentOf(domElement: DomElement, html: string): void;
170
- /**
171
- * Converts the view to the DOM. For all text nodes, not bound elements and document fragments new items will
172
- * be created. For bound elements and document fragments the method will return corresponding items.
173
- *
174
- * @param viewNode View node or document fragment to transform.
175
- * @param options Conversion options.
176
- * @param options.bind Determines whether new elements will be bound.
177
- * @param options.withChildren If `false`, node's and document fragment's children will not be converted.
178
- * @returns Converted node or DocumentFragment.
179
- */
180
- viewToDom(viewNode: ViewNode | ViewDocumentFragment, options?: {
174
+ viewToDom(viewNode: ViewText, options?: {
175
+ bind?: boolean;
176
+ withChildren?: boolean;
177
+ }): DomText;
178
+ viewToDom(viewNode: ViewElement, options?: {
181
179
  bind?: boolean;
182
180
  withChildren?: boolean;
183
- }): DomNode | DomDocumentFragment;
181
+ }): DomElement;
182
+ viewToDom(viewNode: ViewNode, options?: {
183
+ bind?: boolean;
184
+ withChildren?: boolean;
185
+ }): DomNode;
186
+ viewToDom(viewNode: ViewDocumentFragment, options?: {
187
+ bind?: boolean;
188
+ withChildren?: boolean;
189
+ }): DomDocumentFragment;
184
190
  /**
185
191
  * Sets the attribute on a DOM element.
186
192
  *
@@ -212,7 +218,7 @@ export default class DomConverter {
212
218
  * @param options See {@link module:engine/view/domconverter~DomConverter#viewToDom} options parameter.
213
219
  * @returns DOM nodes.
214
220
  */
215
- viewChildrenToDom(viewElement: ViewElement, options?: {
221
+ viewChildrenToDom(viewElement: ViewElement | ViewDocumentFragment, options?: {
216
222
  bind?: boolean;
217
223
  withChildren?: boolean;
218
224
  }): IterableIterator<Node>;
@@ -477,6 +483,12 @@ export default class DomConverter {
477
483
  * @param pattern Pattern matching a view element which should be treated as an inline object.
478
484
  */
479
485
  registerInlineObjectMatcher(pattern: MatcherPattern): void;
486
+ /**
487
+ * Clear temporary custom properties.
488
+ *
489
+ * @internal
490
+ */
491
+ _clearTemporaryCustomProperties(): void;
480
492
  /**
481
493
  * Returns the block {@link module:engine/view/filler filler} node based on the current {@link #blockFillerMode} setting.
482
494
  */
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * @module engine/view/domconverter
7
7
  */
8
- /* globals Node, NodeFilter, DOMParser, Text */
8
+ /* globals Node, NodeFilter, DOMParser */
9
9
  import ViewText from './text';
10
10
  import ViewElement from './element';
11
11
  import ViewUIElement from './uielement';
@@ -16,7 +16,7 @@ import ViewDocumentFragment from './documentfragment';
16
16
  import ViewTreeWalker from './treewalker';
17
17
  import { default as Matcher } from './matcher';
18
18
  import { BR_FILLER, INLINE_FILLER_LENGTH, NBSP_FILLER, MARKED_NBSP_FILLER, getDataWithoutFiller, isInlineFiller, startsWithFiller } from './filler';
19
- import { global, logWarning, indexOf, getAncestors, isText, isComment, isValidAttributeName, first } from '@ckeditor/ckeditor5-utils';
19
+ import { global, logWarning, indexOf, getAncestors, isText, isComment, isValidAttributeName, first, env } from '@ckeditor/ckeditor5-utils';
20
20
  const BR_FILLER_REF = BR_FILLER(global.document); // eslint-disable-line new-cap
21
21
  const NBSP_FILLER_REF = NBSP_FILLER(global.document); // eslint-disable-line new-cap
22
22
  const MARKED_NBSP_FILLER_REF = MARKED_NBSP_FILLER(global.document); // eslint-disable-line new-cap
@@ -70,6 +70,10 @@ export default class DomConverter {
70
70
  * Matcher for inline object view elements. This is an extension of a simple {@link #inlineObjectElements} array of element names.
71
71
  */
72
72
  this._inlineObjectElementMatcher = new Matcher();
73
+ /**
74
+ * Set of elements with temporary custom properties that require clearing after render.
75
+ */
76
+ this._elementsWithTemporaryCustomProperties = new Set();
73
77
  this.document = document;
74
78
  this.renderingMode = renderingMode;
75
79
  this.blockFillerMode = blockFillerMode || (renderingMode === 'editing' ? 'br' : 'nbsp');
@@ -230,57 +234,65 @@ export default class DomConverter {
230
234
  return this._domDocument.createTextNode(textData);
231
235
  }
232
236
  else {
233
- if (this.mapViewToDom(viewNode)) {
234
- return this.mapViewToDom(viewNode);
237
+ const viewElementOrFragment = viewNode;
238
+ if (this.mapViewToDom(viewElementOrFragment)) {
239
+ // Do not reuse element that is marked to not reuse (for example an IMG element
240
+ // so it can immediately display a placeholder background instead of waiting for the new src to load).
241
+ if (viewElementOrFragment.getCustomProperty('editingPipeline:doNotReuseOnce')) {
242
+ this._elementsWithTemporaryCustomProperties.add(viewElementOrFragment);
243
+ }
244
+ else {
245
+ return this.mapViewToDom(viewElementOrFragment);
246
+ }
235
247
  }
236
248
  let domElement;
237
- if (viewNode.is('documentFragment')) {
249
+ if (viewElementOrFragment.is('documentFragment')) {
238
250
  // Create DOM document fragment.
239
251
  domElement = this._domDocument.createDocumentFragment();
240
252
  if (options.bind) {
241
- this.bindDocumentFragments(domElement, viewNode);
253
+ this.bindDocumentFragments(domElement, viewElementOrFragment);
242
254
  }
243
255
  }
244
- else if (viewNode.is('uiElement')) {
245
- if (viewNode.name === '$comment') {
246
- domElement = this._domDocument.createComment(viewNode.getCustomProperty('$rawContent'));
256
+ else if (viewElementOrFragment.is('uiElement')) {
257
+ if (viewElementOrFragment.name === '$comment') {
258
+ domElement = this._domDocument.createComment(viewElementOrFragment.getCustomProperty('$rawContent'));
247
259
  }
248
260
  else {
249
261
  // UIElement has its own render() method (see #799).
250
- domElement = viewNode.render(this._domDocument, this);
262
+ domElement = viewElementOrFragment.render(this._domDocument, this);
251
263
  }
252
264
  if (options.bind) {
253
- this.bindElements(domElement, viewNode);
265
+ this.bindElements(domElement, viewElementOrFragment);
254
266
  }
255
267
  return domElement;
256
268
  }
257
269
  else {
258
270
  // Create DOM element.
259
- if (this._shouldRenameElement(viewNode.name)) {
260
- _logUnsafeElement(viewNode.name);
261
- domElement = this._createReplacementDomElement(viewNode.name);
271
+ if (this._shouldRenameElement(viewElementOrFragment.name)) {
272
+ _logUnsafeElement(viewElementOrFragment.name);
273
+ domElement = this._createReplacementDomElement(viewElementOrFragment.name);
262
274
  }
263
- else if (viewNode.hasAttribute('xmlns')) {
264
- domElement = this._domDocument.createElementNS(viewNode.getAttribute('xmlns'), viewNode.name);
275
+ else if (viewElementOrFragment.hasAttribute('xmlns')) {
276
+ domElement = this._domDocument.createElementNS(viewElementOrFragment.getAttribute('xmlns'), viewElementOrFragment.name);
265
277
  }
266
278
  else {
267
- domElement = this._domDocument.createElement(viewNode.name);
279
+ domElement = this._domDocument.createElement(viewElementOrFragment.name);
268
280
  }
269
281
  // RawElement take care of their children in RawElement#render() method which can be customized
270
282
  // (see https://github.com/ckeditor/ckeditor5/issues/4469).
271
- if (viewNode.is('rawElement')) {
272
- viewNode.render(domElement, this);
283
+ if (viewElementOrFragment.is('rawElement')) {
284
+ viewElementOrFragment.render(domElement, this);
273
285
  }
274
286
  if (options.bind) {
275
- this.bindElements(domElement, viewNode);
287
+ this.bindElements(domElement, viewElementOrFragment);
276
288
  }
277
289
  // Copy element's attributes.
278
- for (const key of viewNode.getAttributeKeys()) {
279
- this.setDomElementAttribute(domElement, key, viewNode.getAttribute(key), viewNode);
290
+ for (const key of viewElementOrFragment.getAttributeKeys()) {
291
+ this.setDomElementAttribute(domElement, key, viewElementOrFragment.getAttribute(key), viewElementOrFragment);
280
292
  }
281
293
  }
282
294
  if (options.withChildren !== false) {
283
- for (const child of this.viewChildrenToDom(viewNode, options)) {
295
+ for (const child of this.viewChildrenToDom(viewElementOrFragment, options)) {
284
296
  domElement.appendChild(child);
285
297
  }
286
298
  }
@@ -526,6 +538,10 @@ export default class DomConverter {
526
538
  * @returns View selection.
527
539
  */
528
540
  domSelectionToView(domSelection) {
541
+ // See: https://github.com/ckeditor/ckeditor5/issues/9635.
542
+ if (isGeckoRestrictedDomSelection(domSelection)) {
543
+ return new ViewSelection([]);
544
+ }
529
545
  // DOM selection might be placed in fake selection container.
530
546
  // If container contains fake selection - return corresponding view selection.
531
547
  if (domSelection.rangeCount === 1) {
@@ -921,6 +937,17 @@ export default class DomConverter {
921
937
  registerInlineObjectMatcher(pattern) {
922
938
  this._inlineObjectElementMatcher.add(pattern);
923
939
  }
940
+ /**
941
+ * Clear temporary custom properties.
942
+ *
943
+ * @internal
944
+ */
945
+ _clearTemporaryCustomProperties() {
946
+ for (const element of this._elementsWithTemporaryCustomProperties) {
947
+ element._removeCustomProperty('editingPipeline:doNotReuseOnce');
948
+ }
949
+ this._elementsWithTemporaryCustomProperties.clear();
950
+ }
924
951
  /**
925
952
  * Returns the block {@link module:engine/view/filler filler} node based on the current {@link #blockFillerMode} setting.
926
953
  */
@@ -1379,6 +1406,27 @@ function _logUnsafeElement(elementName) {
1379
1406
  logWarning('domconverter-unsafe-style-element-detected');
1380
1407
  }
1381
1408
  }
1409
+ /**
1410
+ * In certain cases, Firefox mysteriously assigns so called "restricted objects" to native DOM Range properties.
1411
+ * Any attempt at accessing restricted object's properties causes errors.
1412
+ * See: https://github.com/ckeditor/ckeditor5/issues/9635.
1413
+ */
1414
+ function isGeckoRestrictedDomSelection(domSelection) {
1415
+ if (!env.isGecko) {
1416
+ return false;
1417
+ }
1418
+ if (!domSelection.rangeCount) {
1419
+ return false;
1420
+ }
1421
+ const container = domSelection.getRangeAt(0).startContainer;
1422
+ try {
1423
+ Object.prototype.toString.call(container);
1424
+ }
1425
+ catch (error) {
1426
+ return true;
1427
+ }
1428
+ return false;
1429
+ }
1382
1430
  /**
1383
1431
  * While rendering the editor content, the {@link module:engine/view/domconverter~DomConverter} detected a `<script>` element that may
1384
1432
  * disrupt the editing experience. To avoid this, the `<script>` element was replaced with `<span data-ck-unsafe-element="script"></span>`.
@@ -877,7 +877,7 @@ export default class DowncastWriter {
877
877
  * @internal
878
878
  * @param slotFactory The slot factory.
879
879
  */
880
- _registerSlotFactory(slotFactory: (writer: DowncastWriter, modeOrFilter: string | SlotFilter) => Element): void;
880
+ _registerSlotFactory(slotFactory: (writer: DowncastWriter, modeOrFilter: 'children' | SlotFilter) => Element): void;
881
881
  /**
882
882
  * Clears the registered slot factory.
883
883
  *
@@ -383,7 +383,7 @@ export default class Element extends Node {
383
383
  * @internal
384
384
  * @fires change
385
385
  */
386
- _removeClass(className: string | Array<string>): void;
386
+ _removeClass(className: ArrayOrItem<string>): void;
387
387
  /**
388
388
  * Adds style to the element.
389
389
  *
@@ -230,6 +230,7 @@ export default class Renderer extends ObservableMixin() {
230
230
  // Otherwise, FF may throw an error (https://github.com/ckeditor/ckeditor5/issues/721).
231
231
  this._updateFocus();
232
232
  this._updateSelection();
233
+ this.domConverter._clearTemporaryCustomProperties();
233
234
  this.markedTexts.clear();
234
235
  this.markedAttributes.clear();
235
236
  this.markedChildren.clear();
@@ -258,7 +259,7 @@ export default class Renderer extends ObservableMixin() {
258
259
  // This would produce incorrect element mappings.
259
260
  //
260
261
  // Converting live list to an array to make the list static.
261
- const actualDomChildren = Array.from(this.domConverter.mapViewToDom(viewElement).childNodes);
262
+ const actualDomChildren = Array.from(domElement.childNodes);
262
263
  const expectedDomChildren = Array.from(this.domConverter.viewChildrenToDom(viewElement, { withChildren: false }));
263
264
  const diff = this._diffNodeLists(actualDomChildren, expectedDomChildren);
264
265
  const actions = this._findUpdateActions(diff, actualDomChildren, expectedDomChildren, areSimilarElements);
@@ -272,7 +273,7 @@ export default class Renderer extends ObservableMixin() {
272
273
  // UIElement and RawElement are special cases. Their children are not stored in a view (#799)
273
274
  // so we cannot use them with replacing flow (since they use view children during rendering
274
275
  // which will always result in rendering empty elements).
275
- if (viewChild && !(viewChild.is('uiElement') || viewChild.is('rawElement'))) {
276
+ if (viewChild && !viewChild.is('uiElement') && !viewChild.is('rawElement')) {
276
277
  this._updateElementMappings(viewChild, actualDomChildren[deleteIndex]);
277
278
  }
278
279
  remove(expectedDomChildren[insertIndex]);
@@ -593,7 +593,7 @@ export declare class StylesProcessor {
593
593
  private _mapStyleNames;
594
594
  }
595
595
  /**
596
- * A CSS style property descriptor that contains tuplet of two strings:
596
+ * A CSS style property descriptor that contains tuple of two strings:
597
597
  *
598
598
  * - first string describes property name
599
599
  * - second string describes property value
@@ -24,8 +24,7 @@ export default class StylesMap {
24
24
  */
25
25
  get isEmpty() {
26
26
  const entries = Object.entries(this._styles);
27
- const from = Array.from(entries);
28
- return !from.length;
27
+ return !entries.length;
29
28
  }
30
29
  /**
31
30
  * Number of styles defined.
@@ -45,7 +44,7 @@ export default class StylesMap {
45
44
  */
46
45
  setTo(inlineStyle) {
47
46
  this.clear();
48
- const parsedStyles = Array.from(parseInlineStyles(inlineStyle).entries());
47
+ const parsedStyles = parseInlineStyles(inlineStyle);
49
48
  for (const [key, value] of parsedStyles) {
50
49
  this._styleProcessor.toNormalizedForm(key, value, this._styles);
51
50
  }
@@ -325,7 +324,7 @@ export default class StylesMap {
325
324
  if (!parentObject) {
326
325
  return;
327
326
  }
328
- const isParentEmpty = !Array.from(Object.keys(parentObject)).length;
327
+ const isParentEmpty = !Object.keys(parentObject).length;
329
328
  if (isParentEmpty) {
330
329
  this.remove(parentPath);
331
330
  }
@@ -484,7 +483,7 @@ export class StylesProcessor {
484
483
  ...expandedStyleNames,
485
484
  ...Object.keys(styles)
486
485
  ]);
487
- return Array.from(styleNamesKeysSet.values());
486
+ return Array.from(styleNamesKeysSet);
488
487
  }
489
488
  /**
490
489
  * Returns related style names.
@@ -84,7 +84,8 @@ export default class UIElement extends Element {
84
84
  *
85
85
  * @param domConverter Instance of the DomConverter used to optimize the output.
86
86
  */
87
- render(domDocument, domConverter) {
87
+ render(domDocument, domConverter // eslint-disable-line @typescript-eslint/no-unused-vars
88
+ ) {
88
89
  // Provide basic, default output.
89
90
  return this.toDomElement(domDocument);
90
91
  }
@@ -15,7 +15,7 @@ import Selection, { type PlaceOrOffset, type Selectable, type SelectionOptions }
15
15
  import type { default as Observer, ObserverConstructor } from './observer/observer';
16
16
  import type { StylesProcessor } from './stylesmap';
17
17
  import type Element from './element';
18
- import type Node from './node';
18
+ import type { default as Node } from './node';
19
19
  import type Item from './item';
20
20
  import KeyObserver from './observer/keyobserver';
21
21
  import FakeSelectionObserver from './observer/fakeselectionobserver';