@ckeditor/ckeditor5-engine 29.0.0 → 31.0.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.
@@ -7,10 +7,11 @@
7
7
  * @module engine/view/domconverter
8
8
  */
9
9
 
10
- /* globals document, Node, NodeFilter, Text */
10
+ /* globals document, Node, NodeFilter, DOMParser, Text */
11
11
 
12
12
  import ViewText from './text';
13
13
  import ViewElement from './element';
14
+ import ViewUIElement from './uielement';
14
15
  import ViewPosition from './position';
15
16
  import ViewRange from './range';
16
17
  import ViewSelection from './selection';
@@ -25,9 +26,7 @@ import {
25
26
  import global from '@ckeditor/ckeditor5-utils/src/dom/global';
26
27
  import indexOf from '@ckeditor/ckeditor5-utils/src/dom/indexof';
27
28
  import getAncestors from '@ckeditor/ckeditor5-utils/src/dom/getancestors';
28
- import getCommonAncestor from '@ckeditor/ckeditor5-utils/src/dom/getcommonancestor';
29
29
  import isText from '@ckeditor/ckeditor5-utils/src/dom/istext';
30
- import { isElement } from 'lodash-es';
31
30
 
32
31
  const BR_FILLER_REF = BR_FILLER( document ); // eslint-disable-line new-cap
33
32
  const NBSP_FILLER_REF = NBSP_FILLER( document ); // eslint-disable-line new-cap
@@ -52,7 +51,12 @@ export default class DomConverter {
52
51
  *
53
52
  * @param {module:engine/view/document~Document} document The view document instance.
54
53
  * @param {Object} options An object with configuration options.
55
- * @param {module:engine/view/filler~BlockFillerMode} [options.blockFillerMode='br'] The type of the block filler to use.
54
+ * @param {module:engine/view/filler~BlockFillerMode} [options.blockFillerMode] The type of the block filler to use.
55
+ * Default value depends on the options.renderingMode:
56
+ * 'nbsp' when options.renderingMode == 'data',
57
+ * 'br' when options.renderingMode == 'editing'.
58
+ * @param {'data'|'editing'} [options.renderingMode='editing'] Whether to leave the View-to-DOM conversion result unchanged
59
+ * or improve editing experience by filtering out interactive data.
56
60
  */
57
61
  constructor( document, options = {} ) {
58
62
  /**
@@ -61,12 +65,27 @@ export default class DomConverter {
61
65
  */
62
66
  this.document = document;
63
67
 
68
+ /**
69
+ * Whether to leave the View-to-DOM conversion result unchanged or improve editing experience by filtering out interactive data.
70
+ *
71
+ * @member {'data'|'editing'} module:engine/view/domconverter~DomConverter#renderingMode
72
+ */
73
+ this.renderingMode = options.renderingMode || 'editing';
74
+
75
+ /**
76
+ * Main switch for new rendering approach in the editing view.
77
+ *
78
+ * @protected
79
+ * @member {Boolean}
80
+ */
81
+ this.experimentalRenderingMode = false;
82
+
64
83
  /**
65
84
  * The mode of a block filler used by the DOM converter.
66
85
  *
67
86
  * @member {'br'|'nbsp'|'markedNbsp'} module:engine/view/domconverter~DomConverter#blockFillerMode
68
87
  */
69
- this.blockFillerMode = options.blockFillerMode || 'br';
88
+ this.blockFillerMode = options.blockFillerMode || ( this.renderingMode === 'editing' ? 'br' : 'nbsp' );
70
89
 
71
90
  /**
72
91
  * Elements which are considered pre-formatted elements.
@@ -94,6 +113,23 @@ export default class DomConverter {
94
113
  'td', 'tfoot', 'th', 'thead', 'tr', 'ul'
95
114
  ];
96
115
 
116
+ /**
117
+ * A list of elements that exist inline (in text) but their inner structure cannot be edited because
118
+ * of the way they are rendered by the browser. They are mostly HTML form elements but there are other
119
+ * elements such as `<img>` or `<iframe>` that also have non-editable children or no children whatsoever.
120
+ *
121
+ * Whether an element is considered an inline object has an impact on white space rendering (trimming)
122
+ * around (and inside of it). In short, white spaces in text nodes next to inline objects are not trimmed.
123
+ *
124
+ * You can extend this array if you introduce support for inline object elements which are not yet recognized here.
125
+ *
126
+ * @readonly
127
+ * @member {Array.<String>} module:engine/view/domconverter~DomConverter#inlineObjectElements
128
+ */
129
+ this.inlineObjectElements = [
130
+ 'object', 'iframe', 'input', 'button', 'textarea', 'select', 'option', 'video', 'embed', 'audio', 'img', 'canvas'
131
+ ];
132
+
97
133
  /**
98
134
  * The DOM-to-view mapping.
99
135
  *
@@ -205,6 +241,82 @@ export default class DomConverter {
205
241
  this._viewToDomMapping.set( viewFragment, domFragment );
206
242
  }
207
243
 
244
+ /**
245
+ * Decides whether given pair of attribute key and value should be passed further down the pipeline.
246
+ *
247
+ * @param {String} attributeKey
248
+ * @param {String} attributeValue
249
+ * @returns {Boolean}
250
+ */
251
+ shouldRenderAttribute( attributeKey, attributeValue ) {
252
+ if ( !this.experimentalRenderingMode || this.renderingMode === 'data' ) {
253
+ return true;
254
+ }
255
+
256
+ return !( attributeKey.toLowerCase().startsWith( 'on' ) ||
257
+ attributeValue.match( /(\b)(on\S+)(\s*)=|javascript:|(<\s*)(\/*)script/i ) ||
258
+ attributeValue.match( /data:(?!image\/(png|jpeg|gif|webp))/i )
259
+ );
260
+ }
261
+
262
+ /**
263
+ * Set `domElement`'s content using provided `html` argument. Apply necessary filtering for the editing pipeline.
264
+ *
265
+ * @param {Element} domElement DOM element that should have `html` set as its content.
266
+ * @param {String} html Textual representation of the HTML that will be set on `domElement`.
267
+ */
268
+ setContentOf( domElement, html ) {
269
+ // For data pipeline we pass the HTML as-is.
270
+ if ( !this.experimentalRenderingMode || this.renderingMode === 'data' ) {
271
+ domElement.innerHTML = html;
272
+
273
+ return;
274
+ }
275
+
276
+ const document = new DOMParser().parseFromString( html, 'text/html' );
277
+ const fragment = document.createDocumentFragment();
278
+ const bodyChildNodes = document.body.childNodes;
279
+
280
+ while ( bodyChildNodes.length > 0 ) {
281
+ fragment.appendChild( bodyChildNodes[ 0 ] );
282
+ }
283
+
284
+ const treeWalker = document.createTreeWalker( fragment, NodeFilter.SHOW_ELEMENT );
285
+ const nodes = [];
286
+
287
+ let currentNode;
288
+
289
+ // eslint-disable-next-line no-cond-assign
290
+ while ( currentNode = treeWalker.nextNode() ) {
291
+ nodes.push( currentNode );
292
+ }
293
+
294
+ for ( const currentNode of nodes ) {
295
+ // Go through nodes to remove those that are prohibited in editing pipeline.
296
+ for ( const attributeName of currentNode.getAttributeNames() ) {
297
+ const attributeValue = currentNode.getAttribute( attributeName );
298
+
299
+ if ( !this.shouldRenderAttribute( attributeName, attributeValue ) ) {
300
+ currentNode.removeAttribute( attributeName );
301
+ }
302
+ }
303
+
304
+ const elementName = currentNode.tagName.toLowerCase();
305
+
306
+ // There are certain nodes, that should be renamed to <span> in editing pipeline.
307
+ if ( this._shouldRenameElement( elementName ) ) {
308
+ currentNode.replaceWith( this._createReplacementDomElement( elementName, currentNode ) );
309
+ }
310
+ }
311
+
312
+ // Empty the target element.
313
+ while ( domElement.firstChild ) {
314
+ domElement.firstChild.remove();
315
+ }
316
+
317
+ domElement.append( fragment );
318
+ }
319
+
208
320
  /**
209
321
  * Converts the view to the DOM. For all text nodes, not bound elements and document fragments new items will
210
322
  * be created. For bound elements and document fragments the method will return corresponding items.
@@ -237,8 +349,12 @@ export default class DomConverter {
237
349
  this.bindDocumentFragments( domElement, viewNode );
238
350
  }
239
351
  } else if ( viewNode.is( 'uiElement' ) ) {
240
- // UIElement has its own render() method (see #799).
241
- domElement = viewNode.render( domDocument );
352
+ if ( viewNode.name === '$comment' ) {
353
+ domElement = domDocument.createComment( viewNode.getCustomProperty( '$rawContent' ) );
354
+ } else {
355
+ // UIElement has its own render() method (see #799).
356
+ domElement = viewNode.render( domDocument, this );
357
+ }
242
358
 
243
359
  if ( options.bind ) {
244
360
  this.bindElements( domElement, viewNode );
@@ -247,7 +363,9 @@ export default class DomConverter {
247
363
  return domElement;
248
364
  } else {
249
365
  // Create DOM element.
250
- if ( viewNode.hasAttribute( 'xmlns' ) ) {
366
+ if ( this._shouldRenameElement( viewNode.name ) ) {
367
+ domElement = this._createReplacementDomElement( viewNode.name );
368
+ } else if ( viewNode.hasAttribute( 'xmlns' ) ) {
251
369
  domElement = domDocument.createElementNS( viewNode.getAttribute( 'xmlns' ), viewNode.name );
252
370
  } else {
253
371
  domElement = domDocument.createElement( viewNode.name );
@@ -256,7 +374,7 @@ export default class DomConverter {
256
374
  // RawElement take care of their children in RawElement#render() method which can be customized
257
375
  // (see https://github.com/ckeditor/ckeditor5/issues/4469).
258
376
  if ( viewNode.is( 'rawElement' ) ) {
259
- viewNode.render( domElement );
377
+ viewNode.render( domElement, this );
260
378
  }
261
379
 
262
380
  if ( options.bind ) {
@@ -265,7 +383,13 @@ export default class DomConverter {
265
383
 
266
384
  // Copy element's attributes.
267
385
  for ( const key of viewNode.getAttributeKeys() ) {
268
- domElement.setAttribute( key, viewNode.getAttribute( key ) );
386
+ const value = viewNode.getAttribute( key );
387
+
388
+ if ( !this.shouldRenderAttribute( key, value ) ) {
389
+ continue;
390
+ }
391
+
392
+ domElement.setAttribute( key, value );
269
393
  }
270
394
  }
271
395
 
@@ -406,7 +530,9 @@ export default class DomConverter {
406
530
  * @param {Object} [options] Conversion options.
407
531
  * @param {Boolean} [options.bind=false] Determines whether new elements will be bound.
408
532
  * @param {Boolean} [options.withChildren=true] If `true`, node's and document fragment's children will be converted too.
409
- * @param {Boolean} [options.keepOriginalCase=false] If `false`, node's tag name will be converter to lower case.
533
+ * @param {Boolean} [options.keepOriginalCase=false] If `false`, node's tag name will be converted to lower case.
534
+ * @param {Boolean} [options.skipComments=false] If `false`, comment nodes will be converted to `$comment`
535
+ * {@link module:engine/view/uielement~UIElement view UI elements}.
410
536
  * @returns {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment|null} Converted node or document fragment
411
537
  * or `null` if DOM node is a {@link module:engine/view/filler filler} or the given node is an empty text node.
412
538
  */
@@ -422,6 +548,10 @@ export default class DomConverter {
422
548
  return hostElement;
423
549
  }
424
550
 
551
+ if ( this.isComment( domNode ) && options.skipComments ) {
552
+ return null;
553
+ }
554
+
425
555
  if ( isText( domNode ) ) {
426
556
  if ( isInlineFiller( domNode ) ) {
427
557
  return null;
@@ -430,8 +560,6 @@ export default class DomConverter {
430
560
 
431
561
  return textData === '' ? null : new ViewText( this.document, textData );
432
562
  }
433
- } else if ( this.isComment( domNode ) ) {
434
- return null;
435
563
  } else {
436
564
  if ( this.mapDomToView( domNode ) ) {
437
565
  return this.mapDomToView( domNode );
@@ -448,8 +576,7 @@ export default class DomConverter {
448
576
  }
449
577
  } else {
450
578
  // Create view element.
451
- const viewName = options.keepOriginalCase ? domNode.tagName : domNode.tagName.toLowerCase();
452
- viewElement = new ViewElement( this.document, viewName );
579
+ viewElement = this._createViewElement( domNode, options );
453
580
 
454
581
  if ( options.bind ) {
455
582
  this.bindElements( domNode, viewElement );
@@ -458,13 +585,18 @@ export default class DomConverter {
458
585
  // Copy element's attributes.
459
586
  const attrs = domNode.attributes;
460
587
 
461
- for ( let i = attrs.length - 1; i >= 0; i-- ) {
462
- viewElement._setAttribute( attrs[ i ].name, attrs[ i ].value );
588
+ if ( attrs ) {
589
+ for ( let i = attrs.length - 1; i >= 0; i-- ) {
590
+ viewElement._setAttribute( attrs[ i ].name, attrs[ i ].value );
591
+ }
463
592
  }
464
593
 
465
594
  // Treat this element's content as a raw data if it was registered as such.
466
- if ( options.withChildren !== false && this._rawContentElementMatcher.match( viewElement ) ) {
467
- viewElement._setCustomProperty( '$rawContent', domNode.innerHTML );
595
+ // Comment node is also treated as an element with raw data.
596
+ if ( this._isViewElementWithRawContent( viewElement, options ) || this.isComment( domNode ) ) {
597
+ const rawContent = this.isComment( domNode ) ? domNode.data : domNode.innerHTML;
598
+
599
+ viewElement._setCustomProperty( '$rawContent', rawContent );
468
600
 
469
601
  // Store a DOM node to prevent left trimming of the following text node.
470
602
  this._encounteredRawContentDomNodes.add( domNode );
@@ -575,10 +707,10 @@ export default class DomConverter {
575
707
  * If structures are too different and it is not possible to find corresponding position then `null` will be returned.
576
708
  *
577
709
  * @param {Node} domParent DOM position parent.
578
- * @param {Number} domOffset DOM position offset.
710
+ * @param {Number} [domOffset=0] DOM position offset. You can skip it when converting the inline filler node.
579
711
  * @returns {module:engine/view/position~Position} viewPosition View position.
580
712
  */
581
- domPositionToView( domParent, domOffset ) {
713
+ domPositionToView( domParent, domOffset = 0 ) {
582
714
  if ( this.isBlockFiller( domParent ) ) {
583
715
  return this.domPositionToView( domParent.parentNode, indexOf( domParent ) );
584
716
  }
@@ -1034,8 +1166,8 @@ export default class DomConverter {
1034
1166
  // 1. Replace the first space with a nbsp if the previous node ends with a space or there is no previous node
1035
1167
  // (container element boundary).
1036
1168
  if ( data.charAt( 0 ) == ' ' ) {
1037
- const prevNode = this._getTouchingViewTextNode( node, false );
1038
- const prevEndsWithSpace = prevNode && this._nodeEndsWithSpace( prevNode );
1169
+ const prevNode = this._getTouchingInlineViewNode( node, false );
1170
+ const prevEndsWithSpace = prevNode && prevNode.is( '$textProxy' ) && this._nodeEndsWithSpace( prevNode );
1039
1171
 
1040
1172
  if ( prevEndsWithSpace || !prevNode ) {
1041
1173
  data = '\u00A0' + data.substr( 1 );
@@ -1052,9 +1184,10 @@ export default class DomConverter {
1052
1184
  //
1053
1185
  // More here: https://github.com/ckeditor/ckeditor5-engine/issues/1747.
1054
1186
  if ( data.charAt( data.length - 1 ) == ' ' ) {
1055
- const nextNode = this._getTouchingViewTextNode( node, true );
1187
+ const nextNode = this._getTouchingInlineViewNode( node, true );
1188
+ const nextStartsWithSpace = nextNode && nextNode.is( '$textProxy' ) && nextNode.data.charAt( 0 ) == ' ';
1056
1189
 
1057
- if ( data.charAt( data.length - 2 ) == ' ' || !nextNode || nextNode.data.charAt( 0 ) == ' ' ) {
1190
+ if ( data.charAt( data.length - 2 ) == ' ' || !nextNode || nextStartsWithSpace ) {
1058
1191
  data = data.substr( 0, data.length - 1 ) + '\u00A0';
1059
1192
  }
1060
1193
  }
@@ -1141,14 +1274,17 @@ export default class DomConverter {
1141
1274
  // ` \u00A0` to ensure proper rendering. Since here we convert back, we recognize those pairs and change them back to ` `.
1142
1275
  data = data.replace( / \u00A0/g, ' ' );
1143
1276
 
1277
+ const isNextNodeInlineObjectElement = nextNode && this.isElement( nextNode ) && nextNode.tagName != 'BR';
1278
+ const isNextNodeStartingWithSpace = nextNode && isText( nextNode ) && nextNode.data.charAt( 0 ) == ' ';
1279
+
1144
1280
  // Then, let's change the last nbsp to a space.
1145
- if ( /( |\u00A0)\u00A0$/.test( data ) || !nextNode || ( nextNode.data && nextNode.data.charAt( 0 ) == ' ' ) ) {
1281
+ if ( /( |\u00A0)\u00A0$/.test( data ) || !nextNode || isNextNodeInlineObjectElement || isNextNodeStartingWithSpace ) {
1146
1282
  data = data.replace( /\u00A0$/, ' ' );
1147
1283
  }
1148
1284
 
1149
1285
  // Then, change &nbsp; character that is at the beginning of the text node to space character.
1150
1286
  // We do that replacement only if this is the first node or the previous node ends on whitespace character.
1151
- if ( shouldLeftTrim ) {
1287
+ if ( shouldLeftTrim || prevNode && this.isElement( prevNode ) && prevNode.tagName != 'BR' ) {
1152
1288
  data = data.replace( /^\u00A0/, ' ' );
1153
1289
  }
1154
1290
 
@@ -1163,15 +1299,15 @@ export default class DomConverter {
1163
1299
  *
1164
1300
  * @private
1165
1301
  * @param {Node} node
1166
- * @param {Node} prevNode
1302
+ * @param {Node} prevNode Either DOM text or `<br>` or one of `#inlineObjectElements`.
1167
1303
  */
1168
1304
  _checkShouldLeftTrimDomText( node, prevNode ) {
1169
1305
  if ( !prevNode ) {
1170
1306
  return true;
1171
1307
  }
1172
1308
 
1173
- if ( isElement( prevNode ) ) {
1174
- return true;
1309
+ if ( this.isElement( prevNode ) ) {
1310
+ return prevNode.tagName === 'BR';
1175
1311
  }
1176
1312
 
1177
1313
  // Shouldn't left trim if previous node is a node that was encountered as a raw content node.
@@ -1188,7 +1324,7 @@ export default class DomConverter {
1188
1324
  *
1189
1325
  * @private
1190
1326
  * @param {Node} node
1191
- * @param {Node} nextNode
1327
+ * @param {Node} nextNode Either DOM text or `<br>` or one of `#inlineObjectElements`.
1192
1328
  */
1193
1329
  _checkShouldRightTrimDomText( node, nextNode ) {
1194
1330
  if ( nextNode ) {
@@ -1205,18 +1341,23 @@ export default class DomConverter {
1205
1341
  * @private
1206
1342
  * @param {module:engine/view/text~Text} node Reference node.
1207
1343
  * @param {Boolean} getNext
1208
- * @returns {module:engine/view/text~Text|null} Touching text node or `null` if there is no next or previous touching text node.
1344
+ * @returns {module:engine/view/text~Text|module:engine/view/element~Element|null} Touching text node, an inline object
1345
+ * or `null` if there is no next or previous touching text node.
1209
1346
  */
1210
- _getTouchingViewTextNode( node, getNext ) {
1347
+ _getTouchingInlineViewNode( node, getNext ) {
1211
1348
  const treeWalker = new ViewTreeWalker( {
1212
1349
  startPosition: getNext ? ViewPosition._createAfter( node ) : ViewPosition._createBefore( node ),
1213
1350
  direction: getNext ? 'forward' : 'backward'
1214
1351
  } );
1215
1352
 
1216
1353
  for ( const value of treeWalker ) {
1354
+ // Found an inline object (for example an image).
1355
+ if ( value.item.is( 'element' ) && this.inlineObjectElements.includes( value.item.name ) ) {
1356
+ return value.item;
1357
+ }
1217
1358
  // ViewContainerElement is found on a way to next ViewText node, so given `node` was first/last
1218
1359
  // text node in its container element.
1219
- if ( value.item.is( 'containerElement' ) ) {
1360
+ else if ( value.item.is( 'containerElement' ) ) {
1220
1361
  return null;
1221
1362
  }
1222
1363
  // <br> found – it works like a block boundary, so do not scan further.
@@ -1234,10 +1375,11 @@ export default class DomConverter {
1234
1375
 
1235
1376
  /**
1236
1377
  * Helper function. For the given text node, it finds the closest touching node which is either
1237
- * a text node or a `<br>`. The search is terminated at block element boundaries and if a matching node
1238
- * wasn't found so far, `null` is returned.
1378
+ * a text, `<br>` or an {@link #inlineObjectElements inline object}.
1239
1379
  *
1240
- * In the following DOM structure:
1380
+ * If no such node is found, `null` is returned.
1381
+ *
1382
+ * For instance, in the following DOM structure:
1241
1383
  *
1242
1384
  * <p>foo<b>bar</b><br>bom</p>
1243
1385
  *
@@ -1258,45 +1400,121 @@ export default class DomConverter {
1258
1400
  return null;
1259
1401
  }
1260
1402
 
1261
- const direction = getNext ? 'nextNode' : 'previousNode';
1262
- const document = node.ownerDocument;
1263
- const topmostParent = getAncestors( node )[ 0 ];
1403
+ const stepInto = getNext ? 'firstChild' : 'lastChild';
1404
+ const stepOver = getNext ? 'nextSibling' : 'previousSibling';
1264
1405
 
1265
- const treeWalker = document.createTreeWalker( topmostParent, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, {
1266
- acceptNode( node ) {
1267
- if ( isText( node ) ) {
1268
- return NodeFilter.FILTER_ACCEPT;
1269
- }
1406
+ let skipChildren = true;
1270
1407
 
1271
- if ( node.tagName == 'BR' ) {
1272
- return NodeFilter.FILTER_ACCEPT;
1273
- }
1408
+ do {
1409
+ if ( !skipChildren && node[ stepInto ] ) {
1410
+ node = node[ stepInto ];
1411
+ } else if ( node[ stepOver ] ) {
1412
+ node = node[ stepOver ];
1413
+ skipChildren = false;
1414
+ } else {
1415
+ node = node.parentNode;
1416
+ skipChildren = true;
1417
+ }
1274
1418
 
1275
- return NodeFilter.FILTER_SKIP;
1419
+ if ( !node || this._isBlockElement( node ) ) {
1420
+ return null;
1276
1421
  }
1277
- } );
1422
+ } while (
1423
+ !( isText( node ) || node.tagName == 'BR' || this._isInlineObjectElement( node ) )
1424
+ );
1425
+
1426
+ return node;
1427
+ }
1428
+
1429
+ /**
1430
+ * Returns `true` if a DOM node belongs to {@link #blockElements}. `false` otherwise.
1431
+ *
1432
+ * @private
1433
+ * @param {Node} node
1434
+ * @returns {Boolean}
1435
+ */
1436
+ _isBlockElement( node ) {
1437
+ return this.isElement( node ) && this.blockElements.includes( node.tagName.toLowerCase() );
1438
+ }
1439
+
1440
+ /**
1441
+ * Returns `true` if a DOM node belongs to {@link #inlineObjectElements}. `false` otherwise.
1442
+ *
1443
+ * @private
1444
+ * @param {Node} node
1445
+ * @returns {Boolean}
1446
+ */
1447
+ _isInlineObjectElement( node ) {
1448
+ return this.isElement( node ) && this.inlineObjectElements.includes( node.tagName.toLowerCase() );
1449
+ }
1450
+
1451
+ /**
1452
+ * Creates view element basing on the node type.
1453
+ *
1454
+ * @private
1455
+ * @param {Node} node DOM node to check.
1456
+ * @param {Object} options Conversion options. See {@link module:engine/view/domconverter~DomConverter#domToView} options parameter.
1457
+ * @returns {Element}
1458
+ */
1459
+ _createViewElement( node, options ) {
1460
+ if ( this.isComment( node ) ) {
1461
+ return new ViewUIElement( this.document, '$comment' );
1462
+ }
1463
+
1464
+ const viewName = options.keepOriginalCase ? node.tagName : node.tagName.toLowerCase();
1465
+
1466
+ return new ViewElement( this.document, viewName );
1467
+ }
1468
+
1469
+ /**
1470
+ * Checks if view element's content should be treated as a raw data.
1471
+ *
1472
+ * @private
1473
+ * @param {Element} viewElement View element to check.
1474
+ * @param {Object} options Conversion options. See {@link module:engine/view/domconverter~DomConverter#domToView} options parameter.
1475
+ * @returns {Boolean}
1476
+ */
1477
+ _isViewElementWithRawContent( viewElement, options ) {
1478
+ return options.withChildren !== false && this._rawContentElementMatcher.match( viewElement );
1479
+ }
1278
1480
 
1279
- treeWalker.currentNode = node;
1481
+ /**
1482
+ * Checks whether given element name should be renamed in a current rendering mode.
1483
+ *
1484
+ * @private
1485
+ * @param {String} elementName The name of view element.
1486
+ * @returns {Boolean}
1487
+ */
1488
+ _shouldRenameElement( elementName ) {
1489
+ return this.experimentalRenderingMode && this.renderingMode == 'editing' && elementName == 'script';
1490
+ }
1280
1491
 
1281
- const touchingNode = treeWalker[ direction ]();
1492
+ /**
1493
+ * Return a <span> element with special attribute holding the name of the original element.
1494
+ * Optionally, copy all the attributes of the original element if that element is provided.
1495
+ *
1496
+ * @private
1497
+ * @param {String} elementName The name of view element.
1498
+ * @param {Element} [originalDomElement] The original DOM element to copy attributes and content from.
1499
+ * @returns {Element}
1500
+ */
1501
+ _createReplacementDomElement( elementName, originalDomElement = null ) {
1502
+ const newDomElement = document.createElement( 'span' );
1282
1503
 
1283
- if ( touchingNode !== null ) {
1284
- const lca = getCommonAncestor( node, touchingNode );
1504
+ // Mark the span replacing a script as hidden.
1505
+ newDomElement.setAttribute( 'data-ck-hidden', elementName );
1506
+
1507
+ if ( originalDomElement ) {
1508
+ while ( originalDomElement.firstChild ) {
1509
+ newDomElement.appendChild( originalDomElement.firstChild );
1510
+ }
1285
1511
 
1286
- // If there is common ancestor between the text node and next/prev text node,
1287
- // and there are no block elements on a way from the text node to that ancestor,
1288
- // and there are no block elements on a way from next/prev text node to that ancestor...
1289
- if (
1290
- lca &&
1291
- !_hasDomParentOfType( node, this.blockElements, lca ) &&
1292
- !_hasDomParentOfType( touchingNode, this.blockElements, lca )
1293
- ) {
1294
- // Then they are in the same container element.
1295
- return touchingNode;
1512
+ for ( const attributeName of originalDomElement.getAttributeNames() ) {
1513
+ newDomElement.setAttribute( attributeName, originalDomElement.getAttribute( attributeName ) );
1296
1514
  }
1297
1515
  }
1298
1516
 
1299
- return null;
1517
+ return newDomElement;
1300
1518
  }
1301
1519
  }
1302
1520
 
@@ -1305,14 +1523,9 @@ export default class DomConverter {
1305
1523
  //
1306
1524
  // @param {Node} node
1307
1525
  // @param {Array.<String>} types
1308
- // @param {Boolean} [boundaryParent] Can be given if parents should be checked up to a given element (excluding that element).
1309
1526
  // @returns {Boolean} `true` if such parent exists or `false` if it does not.
1310
- function _hasDomParentOfType( node, types, boundaryParent ) {
1311
- let parents = getAncestors( node );
1312
-
1313
- if ( boundaryParent ) {
1314
- parents = parents.slice( parents.indexOf( boundaryParent ) + 1 );
1315
- }
1527
+ function _hasDomParentOfType( node, types ) {
1528
+ const parents = getAncestors( node );
1316
1529
 
1317
1530
  return parents.some( parent => parent.tagName && types.includes( parent.tagName.toLowerCase() ) );
1318
1531
  }
@@ -1360,7 +1573,7 @@ function hasBlockParent( domNode, blockElements ) {
1360
1573
  *
1361
1574
  * * `br` &ndash; For the `<br data-cke-filler="true">` block filler used in the editing view.
1362
1575
  * * `nbsp` &ndash; For the `&nbsp;` block fillers used in the data.
1363
- * * `markedNbsp` &ndash; For the `&nbsp;` block fillers wrapped in a `<span>` element: `<span data-cke-filler="true">&nbsp;</span>`
1576
+ * * `markedNbsp` &ndash; For the `&nbsp;` block fillers wrapped in `<span>` elements: `<span data-cke-filler="true">&nbsp;</span>`
1364
1577
  * used in the data.
1365
1578
  *
1366
1579
  * @typedef {String} module:engine/view/filler~BlockFillerMode