aboutsummaryrefslogtreecommitdiff
path: root/sources/core/dom
diff options
context:
space:
mode:
Diffstat (limited to 'sources/core/dom')
-rw-r--r--sources/core/dom/comment.js2
-rw-r--r--sources/core/dom/document.js4
-rw-r--r--sources/core/dom/documentfragment.js2
-rw-r--r--sources/core/dom/domobject.js6
-rw-r--r--sources/core/dom/element.js130
-rw-r--r--sources/core/dom/elementpath.js24
-rw-r--r--sources/core/dom/event.js2
-rw-r--r--sources/core/dom/iterator.js22
-rw-r--r--sources/core/dom/node.js12
-rw-r--r--sources/core/dom/nodelist.js19
-rw-r--r--sources/core/dom/range.js271
-rw-r--r--sources/core/dom/rangelist.js4
-rw-r--r--sources/core/dom/text.js4
-rw-r--r--sources/core/dom/walker.js12
-rw-r--r--sources/core/dom/window.js2
15 files changed, 406 insertions, 110 deletions
diff --git a/sources/core/dom/comment.js b/sources/core/dom/comment.js
index 69828c2..4abb453 100644
--- a/sources/core/dom/comment.js
+++ b/sources/core/dom/comment.js
@@ -1,5 +1,5 @@
1/** 1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license 3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */ 4 */
5 5
diff --git a/sources/core/dom/document.js b/sources/core/dom/document.js
index f287245..ebf0bab 100644
--- a/sources/core/dom/document.js
+++ b/sources/core/dom/document.js
@@ -1,5 +1,5 @@
1/** 1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license 3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */ 4 */
5 5
@@ -262,7 +262,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.document.prototype, {
262 * @param {String} html The HTML defining the document content. 262 * @param {String} html The HTML defining the document content.
263 */ 263 */
264 write: function( html ) { 264 write: function( html ) {
265 // Don't leave any history log in IE. (#5657) 265 // Don't leave any history log in IE. (http://dev.ckeditor.com/ticket/5657)
266 this.$.open( 'text/html', 'replace' ); 266 this.$.open( 'text/html', 'replace' );
267 267
268 // Support for custom document.domain in IE. 268 // Support for custom document.domain in IE.
diff --git a/sources/core/dom/documentfragment.js b/sources/core/dom/documentfragment.js
index ffca9e5..1058144 100644
--- a/sources/core/dom/documentfragment.js
+++ b/sources/core/dom/documentfragment.js
@@ -1,5 +1,5 @@
1/** 1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license 3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */ 4 */
5 5
diff --git a/sources/core/dom/domobject.js b/sources/core/dom/domobject.js
index 607e9f3..f4e258a 100644
--- a/sources/core/dom/domobject.js
+++ b/sources/core/dom/domobject.js
@@ -1,5 +1,5 @@
1/** 1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license 3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */ 4 */
5 5
@@ -41,7 +41,7 @@ CKEDITOR.dom.domObject.prototype = ( function() {
41 return function( domEvent ) { 41 return function( domEvent ) {
42 // In FF, when reloading the page with the editor focused, it may 42 // In FF, when reloading the page with the editor focused, it may
43 // throw an error because the CKEDITOR global is not anymore 43 // throw an error because the CKEDITOR global is not anymore
44 // available. So, we check it here first. (#2923) 44 // available. So, we check it here first. (http://dev.ckeditor.com/ticket/2923)
45 if ( typeof CKEDITOR != 'undefined' ) 45 if ( typeof CKEDITOR != 'undefined' )
46 domObject.fire( eventName, new CKEDITOR.dom.event( domEvent ) ); 46 domObject.fire( eventName, new CKEDITOR.dom.event( domEvent ) );
47 }; 47 };
@@ -138,7 +138,7 @@ CKEDITOR.dom.domObject.prototype = ( function() {
138 } 138 }
139 139
140 // Remove events from events object so fire() method will not call 140 // Remove events from events object so fire() method will not call
141 // listeners (#11400). 141 // listeners (http://dev.ckeditor.com/ticket/11400).
142 CKEDITOR.event.prototype.removeAllListeners.call( this ); 142 CKEDITOR.event.prototype.removeAllListeners.call( this );
143 } 143 }
144 }; 144 };
diff --git a/sources/core/dom/element.js b/sources/core/dom/element.js
index b586b02..31451f9 100644
--- a/sources/core/dom/element.js
+++ b/sources/core/dom/element.js
@@ -1,5 +1,5 @@
1/** 1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license 3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */ 4 */
5 5
@@ -308,7 +308,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
308 */ 308 */
309 appendText: function( text ) { 309 appendText: function( text ) {
310 // On IE8 it is impossible to append node to script tag, so we use its text. 310 // On IE8 it is impossible to append node to script tag, so we use its text.
311 // On the contrary, on Safari the text property is unpredictable in links. (#13232) 311 // On the contrary, on Safari the text property is unpredictable in links. (http://dev.ckeditor.com/ticket/13232)
312 if ( this.$.text != null && CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) 312 if ( this.$.text != null && CKEDITOR.env.ie && CKEDITOR.env.version < 9 )
313 this.$.text += text; 313 this.$.text += text;
314 else 314 else
@@ -368,13 +368,36 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
368 range.setEndAfter( parent ); 368 range.setEndAfter( parent );
369 369
370 // Extract it. 370 // Extract it.
371 var docFrag = range.extractContents( false, cloneId || false ); 371 var docFrag = range.extractContents( false, cloneId || false ),
372 tmpElement,
373 current;
372 374
373 // Move the element outside the broken element. 375 // Move the element outside the broken element.
374 range.insertNode( this.remove() ); 376 range.insertNode( this.remove() );
375 377
376 // Re-insert the extracted piece after the element. 378 // In case of Internet Explorer, we must check if there is no background-color
377 docFrag.insertAfterNode( this ); 379 // added to the element. In such case, we have to overwrite it to prevent "switching it off"
380 // by a browser (http://dev.ckeditor.com/ticket/14667).
381 if ( CKEDITOR.env.ie && !CKEDITOR.env.edge ) {
382 tmpElement = new CKEDITOR.dom.element( 'div' );
383
384 while ( current = docFrag.getFirst() ) {
385 if ( current.$.style.backgroundColor ) {
386 // This is a necessary hack to make sure that IE will track backgroundColor CSS property, see
387 // http://dev.ckeditor.com/ticket/14667#comment:8 for more details.
388 current.$.style.backgroundColor = current.$.style.backgroundColor;
389 }
390
391 tmpElement.append( current );
392 }
393
394 // Re-insert the extracted piece after the element.
395 tmpElement.insertAfter( this );
396 tmpElement.remove( true );
397 } else {
398 // Re-insert the extracted piece after the element.
399 docFrag.insertAfterNode( this );
400 }
378 }, 401 },
379 402
380 /** 403 /**
@@ -429,7 +452,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
429 */ 452 */
430 getHtml: function() { 453 getHtml: function() {
431 var retval = this.$.innerHTML; 454 var retval = this.$.innerHTML;
432 // Strip <?xml:namespace> tags in IE. (#3341). 455 // Strip <?xml:namespace> tags in IE. (http://dev.ckeditor.com/ticket/3341).
433 return CKEDITOR.env.ie ? retval.replace( /<\?[^>]*>/g, '' ) : retval; 456 return CKEDITOR.env.ie ? retval.replace( /<\?[^>]*>/g, '' ) : retval;
434 }, 457 },
435 458
@@ -444,7 +467,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
444 getOuterHtml: function() { 467 getOuterHtml: function() {
445 if ( this.$.outerHTML ) { 468 if ( this.$.outerHTML ) {
446 // IE includes the <?xml:namespace> tag in the outerHTML of 469 // IE includes the <?xml:namespace> tag in the outerHTML of
447 // namespaced element. So, we must strip it here. (#3341) 470 // namespaced element. So, we must strip it here. (http://dev.ckeditor.com/ticket/3341)
448 return this.$.outerHTML.replace( /<\?[^>]*>/, '' ); 471 return this.$.outerHTML.replace( /<\?[^>]*>/, '' );
449 } 472 }
450 473
@@ -595,7 +618,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
595 return this.$[ name ]; 618 return this.$[ name ];
596 619
597 case 'style': 620 case 'style':
598 // IE does not return inline styles via getAttribute(). See #2947. 621 // IE does not return inline styles via getAttribute(). See http://dev.ckeditor.com/ticket/2947.
599 return this.$.style.cssText; 622 return this.$.style.cssText;
600 623
601 case 'contenteditable': 624 case 'contenteditable':
@@ -611,6 +634,28 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
611 } )(), 634 } )(),
612 635
613 /** 636 /**
637 * Gets the values of all element attributes.
638 *
639 * @param {Array} exclude The names of attributes to be excluded from the returned object.
640 * @return {Object} An object containing all element attributes with their values.
641 */
642 getAttributes: function( exclude ) {
643 var attributes = {},
644 attrDefs = this.$.attributes,
645 i;
646
647 exclude = CKEDITOR.tools.isArray( exclude ) ? exclude : [];
648
649 for ( i = 0; i < attrDefs.length; i++ ) {
650 if ( CKEDITOR.tools.indexOf( exclude, attrDefs[ i ].name ) === -1 ) {
651 attributes[ attrDefs[ i ].name ] = attrDefs[ i ].value;
652 }
653 }
654
655 return attributes;
656 },
657
658 /**
614 * Gets the nodes list containing all children of this element. 659 * Gets the nodes list containing all children of this element.
615 * 660 *
616 * @returns {CKEDITOR.dom.nodeList} 661 * @returns {CKEDITOR.dom.nodeList}
@@ -634,7 +679,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
634 function( propertyName ) { 679 function( propertyName ) {
635 var style = this.getWindow().$.getComputedStyle( this.$, null ); 680 var style = this.getWindow().$.getComputedStyle( this.$, null );
636 681
637 // Firefox may return null if we call the above on a hidden iframe. (#9117) 682 // Firefox may return null if we call the above on a hidden iframe. (http://dev.ckeditor.com/ticket/9117)
638 return style ? style.getPropertyValue( propertyName ) : ''; 683 return style ? style.getPropertyValue( propertyName ) : '';
639 } : function( propertyName ) { 684 } : function( propertyName ) {
640 return this.$.currentStyle[ CKEDITOR.tools.cssStyleToDomStyle( propertyName ) ]; 685 return this.$.currentStyle[ CKEDITOR.tools.cssStyleToDomStyle( propertyName ) ];
@@ -927,7 +972,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
927 elementWindow, elementWindowFrame; 972 elementWindow, elementWindowFrame;
928 973
929 // Webkit and Opera report non-zero offsetHeight despite that 974 // Webkit and Opera report non-zero offsetHeight despite that
930 // element is inside an invisible iframe. (#4542) 975 // element is inside an invisible iframe. (http://dev.ckeditor.com/ticket/4542)
931 if ( isVisible && CKEDITOR.env.webkit ) { 976 if ( isVisible && CKEDITOR.env.webkit ) {
932 elementWindow = this.getWindow(); 977 elementWindow = this.getWindow();
933 978
@@ -988,7 +1033,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
988 // attribute, which will be marked as "specified", even if the 1033 // attribute, which will be marked as "specified", even if the
989 // outerHTML of the element is not displaying the class attribute. 1034 // outerHTML of the element is not displaying the class attribute.
990 // Note : I was not able to reproduce it outside the editor, 1035 // Note : I was not able to reproduce it outside the editor,
991 // but I've faced it while working on the TC of #1391. 1036 // but I've faced it while working on the TC of http://dev.ckeditor.com/ticket/1391.
992 if ( this.getAttribute( 'class' ) ) { 1037 if ( this.getAttribute( 'class' ) ) {
993 return true; 1038 return true;
994 } 1039 }
@@ -1012,7 +1057,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
1012 var attrs = this.$.attributes, 1057 var attrs = this.$.attributes,
1013 attrsNum = attrs.length; 1058 attrsNum = attrs.length;
1014 1059
1015 // The _moz_dirty attribute might get into the element after pasting (#5455) 1060 // The _moz_dirty attribute might get into the element after pasting (http://dev.ckeditor.com/ticket/5455)
1016 var execludeAttrs = { 'data-cke-expando': 1, _moz_dirty: 1 }; 1061 var execludeAttrs = { 'data-cke-expando': 1, _moz_dirty: 1 };
1017 1062
1018 return attrsNum > 0 && ( attrsNum > 2 || !execludeAttrs[ attrs[ 0 ].nodeName ] || ( attrsNum == 2 && !execludeAttrs[ attrs[ 1 ].nodeName ] ) ); 1063 return attrsNum > 0 && ( attrsNum > 2 || !execludeAttrs[ attrs[ 0 ].nodeName ] || ( attrsNum == 2 && !execludeAttrs[ attrs[ 1 ].nodeName ] ) );
@@ -1119,7 +1164,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
1119 function mergeElements( element, sibling, isNext ) { 1164 function mergeElements( element, sibling, isNext ) {
1120 if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT ) { 1165 if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT ) {
1121 // Jumping over bookmark nodes and empty inline elements, e.g. <b><i></i></b>, 1166 // Jumping over bookmark nodes and empty inline elements, e.g. <b><i></i></b>,
1122 // queuing them to be moved later. (#5567) 1167 // queuing them to be moved later. (http://dev.ckeditor.com/ticket/5567)
1123 var pendingNodes = []; 1168 var pendingNodes = [];
1124 1169
1125 while ( sibling.data( 'cke-bookmark' ) || sibling.isEmptyInlineRemoveable() ) { 1170 while ( sibling.data( 'cke-bookmark' ) || sibling.isEmptyInlineRemoveable() ) {
@@ -1149,7 +1194,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
1149 } 1194 }
1150 1195
1151 return function( inlineOnly ) { 1196 return function( inlineOnly ) {
1152 // Merge empty links and anchors also. (#5567) 1197 // Merge empty links and anchors also. (http://dev.ckeditor.com/ticket/5567)
1153 if ( !( inlineOnly === false || CKEDITOR.dtd.$removeEmpty[ this.getName() ] || this.is( 'a' ) ) ) { 1198 if ( !( inlineOnly === false || CKEDITOR.dtd.$removeEmpty[ this.getName() ] || this.is( 'a' ) ) ) {
1154 return; 1199 return;
1155 } 1200 }
@@ -1208,7 +1253,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
1208 }; 1253 };
1209 } else if ( CKEDITOR.env.ie8Compat && CKEDITOR.env.secure ) { 1254 } else if ( CKEDITOR.env.ie8Compat && CKEDITOR.env.secure ) {
1210 return function( name, value ) { 1255 return function( name, value ) {
1211 // IE8 throws error when setting src attribute to non-ssl value. (#7847) 1256 // IE8 throws error when setting src attribute to non-ssl value. (http://dev.ckeditor.com/ticket/7847)
1212 if ( name == 'src' && value.match( /^http:\/\// ) ) { 1257 if ( name == 'src' && value.match( /^http:\/\// ) ) {
1213 try { 1258 try {
1214 standard.apply( this, arguments ); 1259 standard.apply( this, arguments );
@@ -1292,11 +1337,15 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
1292 */ 1337 */
1293 removeAttributes: function( attributes ) { 1338 removeAttributes: function( attributes ) {
1294 if ( CKEDITOR.tools.isArray( attributes ) ) { 1339 if ( CKEDITOR.tools.isArray( attributes ) ) {
1295 for ( var i = 0; i < attributes.length; i++ ) 1340 for ( var i = 0; i < attributes.length; i++ ) {
1296 this.removeAttribute( attributes[ i ] ); 1341 this.removeAttribute( attributes[ i ] );
1342 }
1297 } else { 1343 } else {
1298 for ( var attr in attributes ) 1344 attributes = attributes || this.getAttributes();
1345
1346 for ( var attr in attributes ) {
1299 attributes.hasOwnProperty( attr ) && this.removeAttribute( attr ); 1347 attributes.hasOwnProperty( attr ) && this.removeAttribute( attr );
1348 }
1300 } 1349 }
1301 }, 1350 },
1302 1351
@@ -1442,7 +1491,8 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
1442 body = doc.getBody(), 1491 body = doc.getBody(),
1443 quirks = doc.$.compatMode == 'BackCompat'; 1492 quirks = doc.$.compatMode == 'BackCompat';
1444 1493
1445 if ( document.documentElement.getBoundingClientRect ) { 1494 if ( document.documentElement.getBoundingClientRect &&
1495 ( CKEDITOR.env.ie ? CKEDITOR.env.version !== 8 : true ) ) {
1446 var box = this.$.getBoundingClientRect(), 1496 var box = this.$.getBoundingClientRect(),
1447 $doc = doc.$, 1497 $doc = doc.$,
1448 $docElem = $doc.documentElement; 1498 $docElem = $doc.documentElement;
@@ -1451,7 +1501,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
1451 clientLeft = $docElem.clientLeft || body.$.clientLeft || 0, 1501 clientLeft = $docElem.clientLeft || body.$.clientLeft || 0,
1452 needAdjustScrollAndBorders = true; 1502 needAdjustScrollAndBorders = true;
1453 1503
1454 // #3804: getBoundingClientRect() works differently on IE and non-IE 1504 // http://dev.ckeditor.com/ticket/3804: getBoundingClientRect() works differently on IE and non-IE
1455 // browsers, regarding scroll positions. 1505 // browsers, regarding scroll positions.
1456 // 1506 //
1457 // On IE, the top position of the <html> element is always 0, no matter 1507 // On IE, the top position of the <html> element is always 0, no matter
@@ -1466,12 +1516,12 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
1466 needAdjustScrollAndBorders = ( quirks && inBody ) || ( !quirks && inDocElem ); 1516 needAdjustScrollAndBorders = ( quirks && inBody ) || ( !quirks && inDocElem );
1467 } 1517 }
1468 1518
1469 // #12747. 1519 // http://dev.ckeditor.com/ticket/12747.
1470 if ( needAdjustScrollAndBorders ) { 1520 if ( needAdjustScrollAndBorders ) {
1471 var scrollRelativeLeft, 1521 var scrollRelativeLeft,
1472 scrollRelativeTop; 1522 scrollRelativeTop;
1473 1523
1474 // See #12758 to know more about document.(documentElement|body).scroll(Left|Top) in Webkit. 1524 // See http://dev.ckeditor.com/ticket/12758 to know more about document.(documentElement|body).scroll(Left|Top) in Webkit.
1475 if ( CKEDITOR.env.webkit || ( CKEDITOR.env.ie && CKEDITOR.env.version >= 12 ) ) { 1525 if ( CKEDITOR.env.webkit || ( CKEDITOR.env.ie && CKEDITOR.env.version >= 12 ) ) {
1476 scrollRelativeLeft = body.$.scrollLeft || $docElem.scrollLeft; 1526 scrollRelativeLeft = body.$.scrollLeft || $docElem.scrollLeft;
1477 scrollRelativeTop = body.$.scrollTop || $docElem.scrollTop; 1527 scrollRelativeTop = body.$.scrollTop || $docElem.scrollTop;
@@ -1553,7 +1603,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
1553 parent.$.clientHeight && parent.$.clientHeight < parent.$.scrollHeight; 1603 parent.$.clientHeight && parent.$.clientHeight < parent.$.scrollHeight;
1554 1604
1555 // Skip body element, which will report wrong clientHeight when containing 1605 // Skip body element, which will report wrong clientHeight when containing
1556 // floated content. (#9523) 1606 // floated content. (http://dev.ckeditor.com/ticket/9523)
1557 if ( overflowed && !parent.is( 'body' ) ) 1607 if ( overflowed && !parent.is( 'body' ) )
1558 this.scrollIntoParent( parent, alignToTop, 1 ); 1608 this.scrollIntoParent( parent, alignToTop, 1 );
1559 1609
@@ -1626,6 +1676,16 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
1626 return parseInt( element.getComputedStyle( 'margin-' + side ) || 0, 10 ) || 0; 1676 return parseInt( element.getComputedStyle( 'margin-' + side ) || 0, 10 ) || 0;
1627 } 1677 }
1628 1678
1679 // [WebKit] Reset stored scrollTop value to not break scrollIntoView() method flow.
1680 // Scrolling breaks when range.select() is used right after element.scrollIntoView(). (http://dev.ckeditor.com/ticket/14659)
1681 if ( CKEDITOR.env.webkit ) {
1682 var editor = this.getEditor( false );
1683
1684 if ( editor ) {
1685 editor._.previousScrollTop = null;
1686 }
1687 }
1688
1629 var win = parent.getWindow(); 1689 var win = parent.getWindow();
1630 1690
1631 var thisPos = screenPos( this, win ), 1691 var thisPos = screenPos( this, win ),
@@ -1797,7 +1857,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
1797 this.getParent( true ) && this.$.parentNode.replaceChild( newNode.$, this.$ ); 1857 this.getParent( true ) && this.$.parentNode.replaceChild( newNode.$, this.$ );
1798 newNode.$[ 'data-cke-expando' ] = this.$[ 'data-cke-expando' ]; 1858 newNode.$[ 'data-cke-expando' ] = this.$[ 'data-cke-expando' ];
1799 this.$ = newNode.$; 1859 this.$ = newNode.$;
1800 // Bust getName's cache. (#8663) 1860 // Bust getName's cache. (http://dev.ckeditor.com/ticket/8663)
1801 delete this.getName; 1861 delete this.getName;
1802 }, 1862 },
1803 1863
@@ -1905,17 +1965,32 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
1905 * CKEDITOR.replace( element ); 1965 * CKEDITOR.replace( element );
1906 * alert( element.getEditor().name ); // 'editor1' 1966 * alert( element.getEditor().name ); // 'editor1'
1907 * 1967 *
1968 * By default this method considers only original DOM elements upon which the editor
1969 * was created. Setting `optimized` parameter to `false` will consider editor editable
1970 * and its children.
1971 *
1972 * @param {Boolean} [optimized=true] If set to `false` it will scan every editor editable.
1908 * @returns {CKEDITOR.editor} An editor instance or null if nothing has been found. 1973 * @returns {CKEDITOR.editor} An editor instance or null if nothing has been found.
1909 */ 1974 */
1910 getEditor: function() { 1975 getEditor: function( optimized ) {
1911 var instances = CKEDITOR.instances, 1976 var instances = CKEDITOR.instances,
1912 name, instance; 1977 name, instance, editable;
1978
1979 optimized = optimized || optimized === undefined;
1913 1980
1914 for ( name in instances ) { 1981 for ( name in instances ) {
1915 instance = instances[ name ]; 1982 instance = instances[ name ];
1916 1983
1917 if ( instance.element.equals( this ) && instance.elementMode != CKEDITOR.ELEMENT_MODE_APPENDTO ) 1984 if ( instance.element.equals( this ) && instance.elementMode != CKEDITOR.ELEMENT_MODE_APPENDTO )
1918 return instance; 1985 return instance;
1986
1987 if ( !optimized ) {
1988 editable = instance.editable();
1989
1990 if ( editable && ( editable.equals( this ) || editable.contains( this ) ) ) {
1991 return instance;
1992 }
1993 }
1919 } 1994 }
1920 1995
1921 return null; 1996 return null;
@@ -2038,7 +2113,8 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
2038 } 2113 }
2039 2114
2040 function getContextualizedSelector( element, selector ) { 2115 function getContextualizedSelector( element, selector ) {
2041 return '#' + element.$.id + ' ' + selector.split( /,\s*/ ).join( ', #' + element.$.id + ' ' ); 2116 var id = CKEDITOR.tools.escapeCss( element.$.id );
2117 return '#' + id + ' ' + selector.split( /,\s*/ ).join( ', #' + id + ' ' );
2042 } 2118 }
2043 2119
2044 var sides = { 2120 var sides = {
@@ -2070,7 +2146,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
2070 function marginAndPaddingSize( type ) { 2146 function marginAndPaddingSize( type ) {
2071 var adjustment = 0; 2147 var adjustment = 0;
2072 for ( var i = 0, len = sides[ type ].length; i < len; i++ ) 2148 for ( var i = 0, len = sides[ type ].length; i < len; i++ )
2073 adjustment += parseInt( this.getComputedStyle( sides[ type ][ i ] ) || 0, 10 ) || 0; 2149 adjustment += parseFloat( this.getComputedStyle( sides[ type ][ i ] ) || 0, 10 ) || 0;
2074 return adjustment; 2150 return adjustment;
2075 } 2151 }
2076 2152
diff --git a/sources/core/dom/elementpath.js b/sources/core/dom/elementpath.js
index 1ee551b..dd50f10 100644
--- a/sources/core/dom/elementpath.js
+++ b/sources/core/dom/elementpath.js
@@ -1,5 +1,5 @@
1/** 1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license 3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */ 4 */
5 5
@@ -57,6 +57,11 @@
57 // Backward compact. 57 // Backward compact.
58 root = root || startNode.getDocument().getBody(); 58 root = root || startNode.getDocument().getBody();
59 59
60 // Assign root value if startNode is null (#424)(https://dev.ckeditor.com/ticket/17028).
61 if ( !e ) {
62 e = root;
63 }
64
60 do { 65 do {
61 if ( e.type == CKEDITOR.NODE_ELEMENT ) { 66 if ( e.type == CKEDITOR.NODE_ELEMENT ) {
62 elements.push( e ); 67 elements.push( e );
@@ -84,7 +89,7 @@
84 block = e; 89 block = e;
85 90
86 if ( pathBlockLimitElements[ elementName ] ) { 91 if ( pathBlockLimitElements[ elementName ] ) {
87 // End level DIV is considered as the block, if no block is available. (#525) 92 // End level DIV is considered as the block, if no block is available. (http://dev.ckeditor.com/ticket/525)
88 // But it must NOT be the root element (checked above). 93 // But it must NOT be the root element (checked above).
89 if ( !block && elementName == 'div' && !checkHasBlock( e ) ) 94 if ( !block && elementName == 'div' && !checkHasBlock( e ) )
90 block = e; 95 block = e;
@@ -181,7 +186,9 @@ CKEDITOR.dom.elementPath.prototype = {
181 * @returns {CKEDITOR.dom.element} The first matched dom element or `null`. 186 * @returns {CKEDITOR.dom.element} The first matched dom element or `null`.
182 */ 187 */
183 contains: function( query, excludeRoot, fromTop ) { 188 contains: function( query, excludeRoot, fromTop ) {
184 var evaluator; 189 var i = 0,
190 evaluator;
191
185 if ( typeof query == 'string' ) 192 if ( typeof query == 'string' )
186 evaluator = function( node ) { 193 evaluator = function( node ) {
187 return node.getName() == query; 194 return node.getName() == query;
@@ -203,14 +210,21 @@ CKEDITOR.dom.elementPath.prototype = {
203 210
204 var elements = this.elements, 211 var elements = this.elements,
205 length = elements.length; 212 length = elements.length;
206 excludeRoot && length--; 213
214 if ( excludeRoot ) {
215 if ( !fromTop ) {
216 length -= 1;
217 } else {
218 i += 1;
219 }
220 }
207 221
208 if ( fromTop ) { 222 if ( fromTop ) {
209 elements = Array.prototype.slice.call( elements, 0 ); 223 elements = Array.prototype.slice.call( elements, 0 );
210 elements.reverse(); 224 elements.reverse();
211 } 225 }
212 226
213 for ( var i = 0; i < length; i++ ) { 227 for ( ; i < length; i++ ) {
214 if ( evaluator( elements[ i ] ) ) 228 if ( evaluator( elements[ i ] ) )
215 return elements[ i ]; 229 return elements[ i ];
216 } 230 }
diff --git a/sources/core/dom/event.js b/sources/core/dom/event.js
index 7cc1bd8..8b1193a 100644
--- a/sources/core/dom/event.js
+++ b/sources/core/dom/event.js
@@ -1,5 +1,5 @@
1/** 1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license 3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */ 4 */
5 5
diff --git a/sources/core/dom/iterator.js b/sources/core/dom/iterator.js
index 1e1b180..9176e33 100644
--- a/sources/core/dom/iterator.js
+++ b/sources/core/dom/iterator.js
@@ -1,5 +1,5 @@
1/** 1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license 3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */ 4 */
5 5
@@ -45,7 +45,7 @@
45 */ 45 */
46 this.forceBrBreak = 0; 46 this.forceBrBreak = 0;
47 47
48 // (#3730). 48 // (http://dev.ckeditor.com/ticket/3730).
49 /** 49 /**
50 * Whether to include `<br>` elements in the enlarged range. Should be 50 * Whether to include `<br>` elements in the enlarged range. Should be
51 * set to `false` when using the iterator in the {@link CKEDITOR#ENTER_BR} mode. 51 * set to `false` when using the iterator in the {@link CKEDITOR#ENTER_BR} mode.
@@ -85,7 +85,7 @@
85 */ 85 */
86 86
87 var beginWhitespaceRegex = /^[\r\n\t ]+$/, 87 var beginWhitespaceRegex = /^[\r\n\t ]+$/,
88 // Ignore bookmark nodes.(#3783) 88 // Ignore bookmark nodes.(http://dev.ckeditor.com/ticket/3783)
89 bookmarkGuard = CKEDITOR.dom.walker.bookmark( false, true ), 89 bookmarkGuard = CKEDITOR.dom.walker.bookmark( false, true ),
90 whitespacesGuard = CKEDITOR.dom.walker.whitespaces( true ), 90 whitespacesGuard = CKEDITOR.dom.walker.whitespaces( true ),
91 skipGuard = function( node ) { 91 skipGuard = function( node ) {
@@ -191,12 +191,12 @@
191 } 191 }
192 192
193 // The range must finish right before the boundary, 193 // The range must finish right before the boundary,
194 // including possibly skipped empty spaces. (#1603) 194 // including possibly skipped empty spaces. (http://dev.ckeditor.com/ticket/1603)
195 if ( range ) { 195 if ( range ) {
196 range.setEndAt( currentNode, CKEDITOR.POSITION_BEFORE_START ); 196 range.setEndAt( currentNode, CKEDITOR.POSITION_BEFORE_START );
197 197
198 // The found boundary must be set as the next one at this 198 // The found boundary must be set as the next one at this
199 // point. (#1717) 199 // point. (http://dev.ckeditor.com/ticket/1717)
200 if ( nodeName != 'br' ) { 200 if ( nodeName != 'br' ) {
201 this._.nextNode = currentNode; 201 this._.nextNode = currentNode;
202 } 202 }
@@ -244,7 +244,7 @@
244 closeRange = 1; 244 closeRange = 1;
245 includeNode = 0; 245 includeNode = 0;
246 isLast = isLast || ( parentNode.equals( lastNode ) ); 246 isLast = isLast || ( parentNode.equals( lastNode ) );
247 // Make sure range includes bookmarks at the end of the block. (#7359) 247 // Make sure range includes bookmarks at the end of the block. (http://dev.ckeditor.com/ticket/7359)
248 range.setEndAt( parentNode, CKEDITOR.POSITION_BEFORE_END ); 248 range.setEndAt( parentNode, CKEDITOR.POSITION_BEFORE_END );
249 break; 249 break;
250 } 250 }
@@ -377,7 +377,7 @@
377 // Here we are checking in guard function whether current element 377 // Here we are checking in guard function whether current element
378 // reach lastNode(default behaviour) and root node to prevent against 378 // reach lastNode(default behaviour) and root node to prevent against
379 // getting out of editor instance root DOM object. 379 // getting out of editor instance root DOM object.
380 // #12484 380 // http://dev.ckeditor.com/ticket/12484
381 function guardFunction( node ) { 381 function guardFunction( node ) {
382 return !( node.equals( lastNode ) || node.equals( rootNode ) ); 382 return !( node.equals( lastNode ) || node.equals( rootNode ) );
383 } 383 }
@@ -397,7 +397,7 @@
397 // Indicate at least one of the range boundaries is inside a preformat block. 397 // Indicate at least one of the range boundaries is inside a preformat block.
398 touchPre, 398 touchPre,
399 399
400 // (#12178) 400 // (http://dev.ckeditor.com/ticket/12178)
401 // Remember if following situation takes place: 401 // Remember if following situation takes place:
402 // * startAtInnerBoundary: <p>foo[</p>... 402 // * startAtInnerBoundary: <p>foo[</p>...
403 // * endAtInnerBoundary: ...<p>]bar</p> 403 // * endAtInnerBoundary: ...<p>]bar</p>
@@ -405,13 +405,13 @@
405 // Note that we test only if path block exist, because we must properly shrink 405 // Note that we test only if path block exist, because we must properly shrink
406 // range containing table and/or table cells. 406 // range containing table and/or table cells.
407 // Note: When range is collapsed there's no way it can be shrinked. 407 // Note: When range is collapsed there's no way it can be shrinked.
408 // By checking if range is collapsed we also prevent #12308. 408 // By checking if range is collapsed we also prevent http://dev.ckeditor.com/ticket/12308.
409 startPath = range.startPath(), 409 startPath = range.startPath(),
410 endPath = range.endPath(), 410 endPath = range.endPath(),
411 startAtInnerBoundary = !range.collapsed && rangeAtInnerBlockBoundary( range, startPath.block ), 411 startAtInnerBoundary = !range.collapsed && rangeAtInnerBlockBoundary( range, startPath.block ),
412 endAtInnerBoundary = !range.collapsed && rangeAtInnerBlockBoundary( range, endPath.block, 1 ); 412 endAtInnerBoundary = !range.collapsed && rangeAtInnerBlockBoundary( range, endPath.block, 1 );
413 413
414 // Shrink the range to exclude harmful "noises" (#4087, #4450, #5435). 414 // Shrink the range to exclude harmful "noises" (http://dev.ckeditor.com/ticket/4087, http://dev.ckeditor.com/ticket/4450, http://dev.ckeditor.com/ticket/5435).
415 range.shrink( CKEDITOR.SHRINK_ELEMENT, true ); 415 range.shrink( CKEDITOR.SHRINK_ELEMENT, true );
416 416
417 if ( startAtInnerBoundary ) 417 if ( startAtInnerBoundary )
@@ -437,7 +437,7 @@
437 437
438 // We may have an empty text node at the end of block due to [3770]. 438 // We may have an empty text node at the end of block due to [3770].
439 // If that node is the lastNode, it would cause our logic to leak to the 439 // If that node is the lastNode, it would cause our logic to leak to the
440 // next block.(#3887) 440 // next block.(http://dev.ckeditor.com/ticket/3887)
441 if ( this._.lastNode && this._.lastNode.type == CKEDITOR.NODE_TEXT && !CKEDITOR.tools.trim( this._.lastNode.getText() ) && this._.lastNode.getParent().isBlockBoundary() ) { 441 if ( this._.lastNode && this._.lastNode.type == CKEDITOR.NODE_TEXT && !CKEDITOR.tools.trim( this._.lastNode.getText() ) && this._.lastNode.getParent().isBlockBoundary() ) {
442 var testRange = this.range.clone(); 442 var testRange = this.range.clone();
443 testRange.moveToPosition( this._.lastNode, CKEDITOR.POSITION_AFTER_END ); 443 testRange.moveToPosition( this._.lastNode, CKEDITOR.POSITION_AFTER_END );
diff --git a/sources/core/dom/node.js b/sources/core/dom/node.js
index 7818b07..69b223e 100644
--- a/sources/core/dom/node.js
+++ b/sources/core/dom/node.js
@@ -1,5 +1,5 @@
1/** 1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license 3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */ 4 */
5 5
@@ -200,7 +200,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, {
200 } 200 }
201 201
202 // IE8 rename HTML5 nodes by adding `:` at the begging of the tag name when the node is cloned, 202 // IE8 rename HTML5 nodes by adding `:` at the begging of the tag name when the node is cloned,
203 // so `<figure>` will be `<:figure>` after 'cloneNode'. We need to fix it (#13101). 203 // so `<figure>` will be `<:figure>` after 'cloneNode'. We need to fix it (http://dev.ckeditor.com/ticket/13101).
204 function renameNodes( node ) { 204 function renameNodes( node ) {
205 if ( node.type != CKEDITOR.NODE_ELEMENT && node.type != CKEDITOR.NODE_DOCUMENT_FRAGMENT ) 205 if ( node.type != CKEDITOR.NODE_ELEMENT && node.type != CKEDITOR.NODE_DOCUMENT_FRAGMENT )
206 return; 206 return;
@@ -804,7 +804,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, {
804 } else if ( trimmed.length < originalLength ) { 804 } else if ( trimmed.length < originalLength ) {
805 child.split( originalLength - trimmed.length ); 805 child.split( originalLength - trimmed.length );
806 806
807 // IE BUG: child.remove() may raise JavaScript errors here. (#81) 807 // IE BUG: child.remove() may raise JavaScript errors here. (http://dev.ckeditor.com/ticket/81)
808 this.$.removeChild( this.$.firstChild ); 808 this.$.removeChild( this.$.firstChild );
809 } 809 }
810 } 810 }
@@ -829,7 +829,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, {
829 child.split( trimmed.length ); 829 child.split( trimmed.length );
830 830
831 // IE BUG: child.getNext().remove() may raise JavaScript errors here. 831 // IE BUG: child.getNext().remove() may raise JavaScript errors here.
832 // (#81) 832 // (http://dev.ckeditor.com/ticket/81)
833 this.$.lastChild.parentNode.removeChild( this.$.lastChild ); 833 this.$.lastChild.parentNode.removeChild( this.$.lastChild );
834 } 834 }
835 } 835 }
@@ -840,7 +840,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, {
840 child = this.$.lastChild; 840 child = this.$.lastChild;
841 841
842 if ( child && child.type == 1 && child.nodeName.toLowerCase() == 'br' ) { 842 if ( child && child.type == 1 && child.nodeName.toLowerCase() == 'br' ) {
843 // Use "eChildNode.parentNode" instead of "node" to avoid IE bug (#324). 843 // Use "eChildNode.parentNode" instead of "node" to avoid IE bug (http://dev.ckeditor.com/ticket/324).
844 child.parentNode.removeChild( child ); 844 child.parentNode.removeChild( child );
845 } 845 }
846 } 846 }
@@ -874,7 +874,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, {
874 if ( this.type != CKEDITOR.NODE_ELEMENT ) 874 if ( this.type != CKEDITOR.NODE_ELEMENT )
875 element = this.getParent(); 875 element = this.getParent();
876 876
877 // Prevent Edge crash (#13609, #13919). 877 // Prevent Edge crash (http://dev.ckeditor.com/ticket/13609, http://dev.ckeditor.com/ticket/13919).
878 if ( CKEDITOR.env.edge && element && element.is( 'textarea', 'input' ) ) { 878 if ( CKEDITOR.env.edge && element && element.is( 'textarea', 'input' ) ) {
879 checkOnlyAttributes = true; 879 checkOnlyAttributes = true;
880 } 880 }
diff --git a/sources/core/dom/nodelist.js b/sources/core/dom/nodelist.js
index 0f91eaa..07af314 100644
--- a/sources/core/dom/nodelist.js
+++ b/sources/core/dom/nodelist.js
@@ -1,11 +1,11 @@
1/** 1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license 3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */ 4 */
5 5
6/** 6/**
7 * Represents a list of {@link CKEDITOR.dom.node} objects. 7 * Represents a list of {@link CKEDITOR.dom.node} objects.
8 * It's a wrapper for native nodes list. 8 * It is a wrapper for a native nodes list.
9 * 9 *
10 * var nodeList = CKEDITOR.document.getBody().getChildren(); 10 * var nodeList = CKEDITOR.document.getBody().getChildren();
11 * alert( nodeList.count() ); // number [0;N] 11 * alert( nodeList.count() ); // number [0;N]
@@ -20,7 +20,7 @@ CKEDITOR.dom.nodeList = function( nativeList ) {
20 20
21CKEDITOR.dom.nodeList.prototype = { 21CKEDITOR.dom.nodeList.prototype = {
22 /** 22 /**
23 * Get count of nodes in this list. 23 * Gets the count of nodes in this list.
24 * 24 *
25 * @returns {Number} 25 * @returns {Number}
26 */ 26 */
@@ -29,7 +29,7 @@ CKEDITOR.dom.nodeList.prototype = {
29 }, 29 },
30 30
31 /** 31 /**
32 * Get node from the list. 32 * Gets the node from the list.
33 * 33 *
34 * @returns {CKEDITOR.dom.node} 34 * @returns {CKEDITOR.dom.node}
35 */ 35 */
@@ -39,5 +39,16 @@ CKEDITOR.dom.nodeList.prototype = {
39 39
40 var $node = this.$[ index ]; 40 var $node = this.$[ index ];
41 return $node ? new CKEDITOR.dom.node( $node ) : null; 41 return $node ? new CKEDITOR.dom.node( $node ) : null;
42 },
43
44 /**
45 * Returns a node list as an array.
46 *
47 * @returns {CKEDITOR.dom.node[]}
48 */
49 toArray: function() {
50 return CKEDITOR.tools.array.map( this.$, function( nativeEl ) {
51 return new CKEDITOR.dom.node( nativeEl );
52 } );
42 } 53 }
43}; 54};
diff --git a/sources/core/dom/range.js b/sources/core/dom/range.js
index b5e8736..742c24c 100644
--- a/sources/core/dom/range.js
+++ b/sources/core/dom/range.js
@@ -1,5 +1,5 @@
1/** 1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license 3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */ 4 */
5 5
@@ -204,9 +204,14 @@ CKEDITOR.dom.range = function( root ) {
204 // This allows us to not think about startNode == endNode case later on. 204 // This allows us to not think about startNode == endNode case later on.
205 // We do that only when cloning, because in other cases we can safely split this text node 205 // We do that only when cloning, because in other cases we can safely split this text node
206 // and hence we can easily handle this case as many others. 206 // and hence we can easily handle this case as many others.
207 if ( isClone && endNode.type == CKEDITOR.NODE_TEXT && startNode.equals( endNode ) ) { 207
208 startNode = range.document.createText( startNode.substring( startOffset, endOffset ) ); 208 // We need to handle situation when selection startNode is type of NODE_ELEMENT (#426).
209 docFrag.append( startNode ); 209 if ( isClone &&
210 endNode.type == CKEDITOR.NODE_TEXT &&
211 ( startNode.equals( endNode ) || ( startNode.type === CKEDITOR.NODE_ELEMENT && startNode.getFirst().equals( endNode ) ) ) ) {
212
213 // Here we should always be inside one text node.
214 docFrag.append( range.document.createText( endNode.substring( startOffset, endOffset ) ) );
210 return; 215 return;
211 } 216 }
212 217
@@ -214,7 +219,7 @@ CKEDITOR.dom.range = function( root ) {
214 // second part. The removal will be handled by the rest of the code. 219 // second part. The removal will be handled by the rest of the code.
215 if ( endNode.type == CKEDITOR.NODE_TEXT ) { 220 if ( endNode.type == CKEDITOR.NODE_TEXT ) {
216 // If Extract or Delete we can split the text node, 221 // If Extract or Delete we can split the text node,
217 // but if Clone (2), then we cannot modify the DOM (#11586) so we mark the text node for cloning. 222 // but if Clone (2), then we cannot modify the DOM (http://dev.ckeditor.com/ticket/11586) so we mark the text node for cloning.
218 if ( !isClone ) { 223 if ( !isClone ) {
219 endNode = endNode.split( endOffset ); 224 endNode = endNode.split( endOffset );
220 } else { 225 } else {
@@ -243,7 +248,7 @@ CKEDITOR.dom.range = function( root ) {
243 // be handled by the rest of the code . 248 // be handled by the rest of the code .
244 if ( startNode.type == CKEDITOR.NODE_TEXT ) { 249 if ( startNode.type == CKEDITOR.NODE_TEXT ) {
245 // If Extract or Delete we can split the text node, 250 // If Extract or Delete we can split the text node,
246 // but if Clone (2), then we cannot modify the DOM (#11586) so we mark 251 // but if Clone (2), then we cannot modify the DOM (http://dev.ckeditor.com/ticket/11586) so we mark
247 // the text node for cloning. 252 // the text node for cloning.
248 if ( !isClone ) { 253 if ( !isClone ) {
249 startNode.split( startOffset ); 254 startNode.split( startOffset );
@@ -373,7 +378,7 @@ CKEDITOR.dom.range = function( root ) {
373 // If we don't do that, in next iterations nodes will be appended to wrong parent. 378 // If we don't do that, in next iterations nodes will be appended to wrong parent.
374 // 379 //
375 // We can just take first child because the algorithm guarantees 380 // We can just take first child because the algorithm guarantees
376 // that this will be the only child on this level. (#13568) 381 // that this will be the only child on this level. (http://dev.ckeditor.com/ticket/13568)
377 levelParent = levelParent.getChild( 0 ); 382 levelParent = levelParent.getChild( 0 );
378 } 383 }
379 } 384 }
@@ -405,7 +410,7 @@ CKEDITOR.dom.range = function( root ) {
405 410
406 // When Extracting, move the removed node to the docFrag. 411 // When Extracting, move the removed node to the docFrag.
407 if ( isExtract ) { 412 if ( isExtract ) {
408 newParent.append( node ); 413 newParent.append( node, toStart );
409 } 414 }
410 } 415 }
411 416
@@ -574,7 +579,7 @@ CKEDITOR.dom.range = function( root ) {
574 // Tolerant bogus br when checking at the end of block. 579 // Tolerant bogus br when checking at the end of block.
575 // Reject any text node unless it's being bookmark 580 // Reject any text node unless it's being bookmark
576 // OR it's spaces. 581 // OR it's spaces.
577 // Reject any element unless it's being invisible empty. (#3883) 582 // Reject any element unless it's being invisible empty. (http://dev.ckeditor.com/ticket/3883)
578 return !checkStart && isBogus( node ) || 583 return !checkStart && isBogus( node ) ||
579 node.type == CKEDITOR.NODE_ELEMENT && 584 node.type == CKEDITOR.NODE_ELEMENT &&
580 node.is( CKEDITOR.dtd.$removeEmpty ); 585 node.is( CKEDITOR.dtd.$removeEmpty );
@@ -1052,7 +1057,7 @@ CKEDITOR.dom.range = function( root ) {
1052 } 1057 }
1053 1058
1054 // Sometimes the endNode will come right before startNode for collapsed 1059 // Sometimes the endNode will come right before startNode for collapsed
1055 // ranges. Fix it. (#3780) 1060 // ranges. Fix it. (http://dev.ckeditor.com/ticket/3780)
1056 if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING ) 1061 if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING )
1057 startNode = endNode; 1062 startNode = endNode;
1058 1063
@@ -1201,7 +1206,8 @@ CKEDITOR.dom.range = function( root ) {
1201 /** 1206 /**
1202 * Expands the range so that partial units are completely contained. 1207 * Expands the range so that partial units are completely contained.
1203 * 1208 *
1204 * @param unit {Number} The unit type to expand with. 1209 * @param {Number} unit The unit type to expand with. Use one of following values: {@link CKEDITOR#ENLARGE_BLOCK_CONTENTS},
1210 * {@link CKEDITOR#ENLARGE_ELEMENT}, {@link CKEDITOR#ENLARGE_INLINE}, {@link CKEDITOR#ENLARGE_LIST_ITEM_CONTENTS}.
1205 * @param {Boolean} [excludeBrs=false] Whether include line-breaks when expanding. 1211 * @param {Boolean} [excludeBrs=false] Whether include line-breaks when expanding.
1206 */ 1212 */
1207 enlarge: function( unit, excludeBrs ) { 1213 enlarge: function( unit, excludeBrs ) {
@@ -1323,13 +1329,13 @@ CKEDITOR.dom.range = function( root ) {
1323 1329
1324 isWhiteSpace = /[\s\ufeff]$/.test( siblingText ); 1330 isWhiteSpace = /[\s\ufeff]$/.test( siblingText );
1325 } else { 1331 } else {
1326 // #12221 (Chrome) plus #11111 (Safari). 1332 // http://dev.ckeditor.com/ticket/12221 (Chrome) plus http://dev.ckeditor.com/ticket/11111 (Safari).
1327 var offsetWidth0 = CKEDITOR.env.webkit ? 1 : 0; 1333 var offsetWidth0 = CKEDITOR.env.webkit ? 1 : 0;
1328 1334
1329 // If this is a visible element. 1335 // If this is a visible element.
1330 // We need to check for the bookmark attribute because IE insists on 1336 // We need to check for the bookmark attribute because IE insists on
1331 // rendering the display:none nodes we use for bookmarks. (#3363) 1337 // rendering the display:none nodes we use for bookmarks. (http://dev.ckeditor.com/ticket/3363)
1332 // Line-breaks (br) are rendered with zero width, which we don't want to include. (#7041) 1338 // Line-breaks (br) are rendered with zero width, which we don't want to include. (http://dev.ckeditor.com/ticket/7041)
1333 if ( ( sibling.$.offsetWidth > offsetWidth0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) ) { 1339 if ( ( sibling.$.offsetWidth > offsetWidth0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) ) {
1334 // We'll accept it only if we need 1340 // We'll accept it only if we need
1335 // whitespace, and this is an inline 1341 // whitespace, and this is an inline
@@ -1398,7 +1404,7 @@ CKEDITOR.dom.range = function( root ) {
1398 1404
1399 // Process the end boundary. This is basically the same 1405 // Process the end boundary. This is basically the same
1400 // code used for the start boundary, with small changes to 1406 // code used for the start boundary, with small changes to
1401 // make it work in the oposite side (to the right). This 1407 // make it work in the opposite side (to the right). This
1402 // makes it difficult to reuse the code here. So, fixes to 1408 // makes it difficult to reuse the code here. So, fixes to
1403 // the above code are likely to be replicated here. 1409 // the above code are likely to be replicated here.
1404 1410
@@ -1481,7 +1487,7 @@ CKEDITOR.dom.range = function( root ) {
1481 } 1487 }
1482 } 1488 }
1483 } else { 1489 } else {
1484 // Get the node right after the boudary to be checked 1490 // Get the node right after the boundary to be checked
1485 // first. 1491 // first.
1486 sibling = container.getChild( offset ); 1492 sibling = container.getChild( offset );
1487 1493
@@ -1524,8 +1530,8 @@ CKEDITOR.dom.range = function( root ) {
1524 } else if ( sibling.type == CKEDITOR.NODE_ELEMENT ) { 1530 } else if ( sibling.type == CKEDITOR.NODE_ELEMENT ) {
1525 // If this is a visible element. 1531 // If this is a visible element.
1526 // We need to check for the bookmark attribute because IE insists on 1532 // We need to check for the bookmark attribute because IE insists on
1527 // rendering the display:none nodes we use for bookmarks. (#3363) 1533 // rendering the display:none nodes we use for bookmarks. (http://dev.ckeditor.com/ticket/3363)
1528 // Line-breaks (br) are rendered with zero width, which we don't want to include. (#7041) 1534 // Line-breaks (br) are rendered with zero width, which we don't want to include. (http://dev.ckeditor.com/ticket/7041)
1529 if ( ( sibling.$.offsetWidth > 0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) ) { 1535 if ( ( sibling.$.offsetWidth > 0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) ) {
1530 // We'll accept it only if we need 1536 // We'll accept it only if we need
1531 // whitespace, and this is an inline 1537 // whitespace, and this is an inline
@@ -1616,7 +1622,7 @@ CKEDITOR.dom.range = function( root ) {
1616 boundaryGuard = function( node ) { 1622 boundaryGuard = function( node ) {
1617 // We should not check contents of non-editable elements. It may happen 1623 // We should not check contents of non-editable elements. It may happen
1618 // that inline widget has display:table child which should not block range#enlarge. 1624 // that inline widget has display:table child which should not block range#enlarge.
1619 // When encoutered non-editable element... 1625 // When encountered non-editable element...
1620 if ( node.type == CKEDITOR.NODE_ELEMENT && node.getAttribute( 'contenteditable' ) == 'false' ) { 1626 if ( node.type == CKEDITOR.NODE_ELEMENT && node.getAttribute( 'contenteditable' ) == 'false' ) {
1621 if ( inNonEditable ) { 1627 if ( inNonEditable ) {
1622 // ... in which we already were, reset it (because we're leaving it) and return. 1628 // ... in which we already were, reset it (because we're leaving it) and return.
@@ -1638,7 +1644,7 @@ CKEDITOR.dom.range = function( root ) {
1638 blockBoundary = node; 1644 blockBoundary = node;
1639 return retval; 1645 return retval;
1640 }, 1646 },
1641 // Record the encounted 'tailBr' for later use. 1647 // Record the encountered 'tailBr' for later use.
1642 tailBrGuard = function( node ) { 1648 tailBrGuard = function( node ) {
1643 var retval = boundaryGuard( node ); 1649 var retval = boundaryGuard( node );
1644 if ( !retval && node.is && node.is( 'br' ) ) 1650 if ( !retval && node.is && node.is( 'br' ) )
@@ -1659,7 +1665,7 @@ CKEDITOR.dom.range = function( root ) {
1659 this.setStartAt( blockBoundary, !blockBoundary.is( 'br' ) && ( !enlargeable && this.checkStartOfBlock() || 1665 this.setStartAt( blockBoundary, !blockBoundary.is( 'br' ) && ( !enlargeable && this.checkStartOfBlock() ||
1660 enlargeable && blockBoundary.contains( enlargeable ) ) ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_AFTER_END ); 1666 enlargeable && blockBoundary.contains( enlargeable ) ) ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_AFTER_END );
1661 1667
1662 // Avoid enlarging the range further when end boundary spans right after the BR. (#7490) 1668 // Avoid enlarging the range further when end boundary spans right after the BR. (http://dev.ckeditor.com/ticket/7490)
1663 if ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) { 1669 if ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) {
1664 var theRange = this.clone(); 1670 var theRange = this.clone();
1665 walker = new CKEDITOR.dom.walker( theRange ); 1671 walker = new CKEDITOR.dom.walker( theRange );
@@ -1714,18 +1720,28 @@ CKEDITOR.dom.range = function( root ) {
1714 }, 1720 },
1715 1721
1716 /** 1722 /**
1717 * Descrease the range to make sure that boundaries 1723 * Decreases the range to make sure that boundaries
1718 * always anchor beside text nodes or innermost element. 1724 * always anchor beside text nodes or the innermost element.
1719 * 1725 *
1720 * @param {Number} mode The shrinking mode ({@link CKEDITOR#SHRINK_ELEMENT} or {@link CKEDITOR#SHRINK_TEXT}). 1726 * @param {Number} mode The shrinking mode ({@link CKEDITOR#SHRINK_ELEMENT} or {@link CKEDITOR#SHRINK_TEXT}).
1721 * 1727 *
1722 * * {@link CKEDITOR#SHRINK_ELEMENT} - Shrink the range boundaries to the edge of the innermost element. 1728 * * {@link CKEDITOR#SHRINK_ELEMENT} &ndash; Shrinks the range boundaries to the edge of the innermost element.
1723 * * {@link CKEDITOR#SHRINK_TEXT} - Shrink the range boudaries to anchor by the side of enclosed text 1729 * * {@link CKEDITOR#SHRINK_TEXT} &ndash; Shrinks the range boundaries to anchor by the side of enclosed text
1724 * node, range remains if there's no text nodes on boundaries at all. 1730 * node. The range remains if there are no text nodes available on boundaries.
1725 * 1731 *
1726 * @param {Boolean} selectContents Whether result range anchors at the inner OR outer boundary of the node. 1732 * @param {Boolean} [selectContents=false] Whether the resulting range anchors at the inner OR outer boundary of the node.
1733 * @param {Boolean/Object} [options=true] If this parameter is of a Boolean type, it is treated as
1734 * `options.shrinkOnBlockBoundary`. This parameter was added in 4.7.0.
1735 * @param {Boolean} [options.shrinkOnBlockBoundary=true] Whether the block boundary should be included in
1736 * the shrunk range.
1737 * @param {Boolean} [options.skipBogus=false] Whether bogus `<br>` elements should be ignored while
1738 * `mode` is set to {@link CKEDITOR#SHRINK_TEXT}. This option was added in 4.7.0.
1727 */ 1739 */
1728 shrink: function( mode, selectContents, shrinkOnBlockBoundary ) { 1740 shrink: function( mode, selectContents, options ) {
1741 var shrinkOnBlockBoundary = typeof options === 'boolean' ? options :
1742 ( options && typeof options.shrinkOnBlockBoundary === 'boolean' ? options.shrinkOnBlockBoundary : true ),
1743 skipBogus = options && options.skipBogus;
1744
1729 // Unable to shrink a collapsed range. 1745 // Unable to shrink a collapsed range.
1730 if ( !this.collapsed ) { 1746 if ( !this.collapsed ) {
1731 mode = mode || CKEDITOR.SHRINK_TEXT; 1747 mode = mode || CKEDITOR.SHRINK_TEXT;
@@ -1748,7 +1764,7 @@ CKEDITOR.dom.range = function( root ) {
1748 walkerRange.setStartAfter( startContainer ); 1764 walkerRange.setStartAfter( startContainer );
1749 else { 1765 else {
1750 // Enlarge the range properly to avoid walker making 1766 // Enlarge the range properly to avoid walker making
1751 // DOM changes caused by triming the text nodes later. 1767 // DOM changes caused by trimming the text nodes later.
1752 walkerRange.setStartBefore( startContainer ); 1768 walkerRange.setStartBefore( startContainer );
1753 moveStart = 0; 1769 moveStart = 0;
1754 } 1770 }
@@ -1766,7 +1782,8 @@ CKEDITOR.dom.range = function( root ) {
1766 } 1782 }
1767 1783
1768 var walker = new CKEDITOR.dom.walker( walkerRange ), 1784 var walker = new CKEDITOR.dom.walker( walkerRange ),
1769 isBookmark = CKEDITOR.dom.walker.bookmark(); 1785 isBookmark = CKEDITOR.dom.walker.bookmark(),
1786 isBogus = CKEDITOR.dom.walker.bogus();
1770 1787
1771 walker.evaluator = function( node ) { 1788 walker.evaluator = function( node ) {
1772 return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ? CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT ); 1789 return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ? CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT );
@@ -1774,6 +1791,11 @@ CKEDITOR.dom.range = function( root ) {
1774 1791
1775 var currentElement; 1792 var currentElement;
1776 walker.guard = function( node, movingOut ) { 1793 walker.guard = function( node, movingOut ) {
1794 // Skipping bogus before other cases (http://dev.ckeditor.com/ticket/17010).
1795 if ( skipBogus && isBogus( node ) ) {
1796 return true;
1797 }
1798
1777 if ( isBookmark( node ) ) 1799 if ( isBookmark( node ) )
1778 return true; 1800 return true;
1779 1801
@@ -1815,7 +1837,7 @@ CKEDITOR.dom.range = function( root ) {
1815 1837
1816 /** 1838 /**
1817 * Inserts a node at the start of the range. The range will be expanded 1839 * Inserts a node at the start of the range. The range will be expanded
1818 * the contain the node. 1840 * to contain the node.
1819 * 1841 *
1820 * @param {CKEDITOR.dom.node} node 1842 * @param {CKEDITOR.dom.node} node
1821 */ 1843 */
@@ -1842,7 +1864,7 @@ CKEDITOR.dom.range = function( root ) {
1842 }, 1864 },
1843 1865
1844 /** 1866 /**
1845 * Moves the range to given position according to specified node. 1867 * Moves the range to a given position according to the specified node.
1846 * 1868 *
1847 * // HTML: <p>Foo <b>bar</b></p> 1869 * // HTML: <p>Foo <b>bar</b></p>
1848 * range.moveToPosition( elB, CKEDITOR.POSITION_BEFORE_START ); 1870 * range.moveToPosition( elB, CKEDITOR.POSITION_BEFORE_START );
@@ -1850,7 +1872,7 @@ CKEDITOR.dom.range = function( root ) {
1850 * 1872 *
1851 * See also {@link #setStartAt} and {@link #setEndAt}. 1873 * See also {@link #setStartAt} and {@link #setEndAt}.
1852 * 1874 *
1853 * @param {CKEDITOR.dom.node} node The node according to which position will be set. 1875 * @param {CKEDITOR.dom.node} node The node according to which the position will be set.
1854 * @param {Number} position One of {@link CKEDITOR#POSITION_BEFORE_START}, 1876 * @param {Number} position One of {@link CKEDITOR#POSITION_BEFORE_START},
1855 * {@link CKEDITOR#POSITION_AFTER_START}, {@link CKEDITOR#POSITION_BEFORE_END}, 1877 * {@link CKEDITOR#POSITION_AFTER_START}, {@link CKEDITOR#POSITION_BEFORE_END},
1856 * {@link CKEDITOR#POSITION_AFTER_END}. 1878 * {@link CKEDITOR#POSITION_AFTER_END}.
@@ -2117,7 +2139,7 @@ CKEDITOR.dom.range = function( root ) {
2117 // So even if the initial range was placed before the bogus <br>, after creating the bookmark it 2139 // So even if the initial range was placed before the bogus <br>, after creating the bookmark it
2118 // is placed before the bookmark. 2140 // is placed before the bookmark.
2119 // Fortunately, getBogus() is able to skip the bookmark so it finds the bogus <br> in this case. 2141 // Fortunately, getBogus() is able to skip the bookmark so it finds the bogus <br> in this case.
2120 // We remove incorrectly placed one and add a brand new one. (#13001) 2142 // We remove incorrectly placed one and add a brand new one. (http://dev.ckeditor.com/ticket/13001)
2121 var bogus = fixedBlock.getBogus(); 2143 var bogus = fixedBlock.getBogus();
2122 if ( bogus ) { 2144 if ( bogus ) {
2123 bogus.remove(); 2145 bogus.remove();
@@ -2339,7 +2361,7 @@ CKEDITOR.dom.range = function( root ) {
2339 this.trim( 0, 1 ); 2361 this.trim( 0, 1 );
2340 } 2362 }
2341 2363
2342 // Antecipate the trim() call here, so the walker will not make 2364 // Anticipate the trim() call here, so the walker will not make
2343 // changes to the DOM, which would not get reflected into this 2365 // changes to the DOM, which would not get reflected into this
2344 // range otherwise. 2366 // range otherwise.
2345 this.trim(); 2367 this.trim();
@@ -2378,7 +2400,7 @@ CKEDITOR.dom.range = function( root ) {
2378 this.trim( 1, 0 ); 2400 this.trim( 1, 0 );
2379 } 2401 }
2380 2402
2381 // Antecipate the trim() call here, so the walker will not make 2403 // Anticipate the trim() call here, so the walker will not make
2382 // changes to the DOM, which would not get reflected into this 2404 // changes to the DOM, which would not get reflected into this
2383 // range otherwise. 2405 // range otherwise.
2384 this.trim(); 2406 this.trim();
@@ -2635,7 +2657,7 @@ CKEDITOR.dom.range = function( root ) {
2635 getEnclosedNode: function() { 2657 getEnclosedNode: function() {
2636 var walkerRange = this.clone(); 2658 var walkerRange = this.clone();
2637 2659
2638 // Optimize and analyze the range to avoid DOM destructive nature of walker. (#5780) 2660 // Optimize and analyze the range to avoid DOM destructive nature of walker. (http://dev.ckeditor.com/ticket/5780)
2639 walkerRange.optimize(); 2661 walkerRange.optimize();
2640 if ( walkerRange.startContainer.type != CKEDITOR.NODE_ELEMENT || walkerRange.endContainer.type != CKEDITOR.NODE_ELEMENT ) 2662 if ( walkerRange.startContainer.type != CKEDITOR.NODE_ELEMENT || walkerRange.endContainer.type != CKEDITOR.NODE_ELEMENT )
2641 return null; 2663 return null;
@@ -2702,6 +2724,53 @@ CKEDITOR.dom.range = function( root ) {
2702 getPreviousEditableNode: getNextEditableNode( 1 ), 2724 getPreviousEditableNode: getNextEditableNode( 1 ),
2703 2725
2704 /** 2726 /**
2727 * Returns any table element, like `td`, `tbody`, `table` etc. from a given range. The element
2728 * is returned only if the range is contained within one table (might be a nested
2729 * table, but it cannot be two different tables on the same DOM level).
2730 *
2731 * @private
2732 * @since 4.7
2733 * @param {Object} [tableElements] Mapping of element names that should be considered.
2734 * @returns {CKEDITOR.dom.element/null}
2735 */
2736 _getTableElement: function( tableElements ) {
2737 tableElements = tableElements || {
2738 td: 1,
2739 th: 1,
2740 tr: 1,
2741 tbody: 1,
2742 thead: 1,
2743 tfoot: 1,
2744 table: 1
2745 };
2746
2747 var start = this.startContainer,
2748 end = this.endContainer,
2749 startTable = start.getAscendant( 'table', true ),
2750 endTable = end.getAscendant( 'table', true );
2751
2752 // Super weird edge case in Safari: if there is a table with only one cell inside and that cell
2753 // is selected, then the end boundary of the table is moved into editor's editable.
2754 // That case is also present when selecting the last cell inside nested table.
2755 if ( CKEDITOR.env.safari && startTable && end.equals( this.root ) ) {
2756 return start.getAscendant( tableElements, true );
2757 }
2758
2759 if ( this.getEnclosedNode() ) {
2760 return this.getEnclosedNode().getAscendant( tableElements, true );
2761 }
2762
2763 // Ensure that selection starts and ends in the same table or one of the table is inside the other.
2764 if ( startTable && endTable && ( startTable.equals( endTable ) || startTable.contains( endTable ) ||
2765 endTable.contains( startTable ) ) ) {
2766
2767 return start.getAscendant( tableElements, true );
2768 }
2769
2770 return null;
2771 },
2772
2773 /**
2705 * Scrolls the start of current range into view. 2774 * Scrolls the start of current range into view.
2706 */ 2775 */
2707 scrollIntoView: function() { 2776 scrollIntoView: function() {
@@ -2783,9 +2852,112 @@ CKEDITOR.dom.range = function( root ) {
2783 } 2852 }
2784 // %REMOVE_END% 2853 // %REMOVE_END%
2785 this.endContainer = endContainer; 2854 this.endContainer = endContainer;
2855 },
2856
2857 /**
2858 * Looks for elements matching the `query` selector within a range.
2859 *
2860 * @since 4.5.11
2861 * @private
2862 * @param {String} query
2863 * @param {Boolean} [includeNonEditables=false] Whether elements with `contenteditable` set to `false` should
2864 * be included.
2865 * @returns {CKEDITOR.dom.element[]}
2866 */
2867 _find: function( query, includeNonEditables ) {
2868 var ancestor = this.getCommonAncestor(),
2869 boundaries = this.getBoundaryNodes(),
2870 // Contrary to CKEDITOR.dom.element#find we're returning array, that's because NodeList is immutable, and we need
2871 // to do some filtering in returned list.
2872 ret = [],
2873 curItem,
2874 i,
2875 initialMatches,
2876 isStartGood,
2877 isEndGood;
2878
2879 if ( ancestor && ancestor.find ) {
2880 initialMatches = ancestor.find( query );
2881
2882 for ( i = 0; i < initialMatches.count(); i++ ) {
2883 curItem = initialMatches.getItem( i );
2884
2885 // Using isReadOnly() method to filterout non editables. It checks isContentEditable including all browser quirks.
2886 if ( !includeNonEditables && curItem.isReadOnly() ) {
2887 continue;
2888 }
2889
2890 // It's not enough to get elements from common ancestor, because it might contain too many matches.
2891 // We need to ensure that returned items are between boundary points.
2892 isStartGood = ( curItem.getPosition( boundaries.startNode ) & CKEDITOR.POSITION_FOLLOWING ) || boundaries.startNode.equals( curItem );
2893 isEndGood = ( curItem.getPosition( boundaries.endNode ) & ( CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_IS_CONTAINED ) ) || boundaries.endNode.equals( curItem );
2894
2895 if ( isStartGood && isEndGood ) {
2896 ret.push( curItem );
2897 }
2898 }
2899 }
2900
2901 return ret;
2786 } 2902 }
2787 }; 2903 };
2788 2904
2905 /**
2906 * Merges every subsequent range in given set, returning a smaller array of ranges.
2907 *
2908 * Note that each range in the returned value will be enlarged with `CKEDITOR.ENLARGE_ELEMENT` value.
2909 *
2910 * @since 4.7.0
2911 * @static
2912 * @param {CKEDITOR.dom.range[]} ranges
2913 * @returns {CKEDITOR.dom.range[]} Set of merged ranges.
2914 * @member CKEDITOR.dom.range
2915 */
2916 CKEDITOR.dom.range.mergeRanges = function( ranges ) {
2917 return CKEDITOR.tools.array.reduce( ranges, function( ret, rng ) {
2918 // Last range ATM.
2919 var lastRange = ret[ ret.length - 1 ],
2920 isContinuation = false;
2921
2922 // Make a clone, we don't want to modify input.
2923 rng = rng.clone();
2924 rng.enlarge( CKEDITOR.ENLARGE_ELEMENT );
2925
2926 if ( lastRange ) {
2927 // The trick is to create a range spanning the gap between the two ranges. Then iterate over
2928 // each node found in this gap. If it contains anything other than whitespace, then it means it
2929 // is not a continuation.
2930 var gapRange = new CKEDITOR.dom.range( rng.root ),
2931 walker = new CKEDITOR.dom.walker( gapRange ),
2932 isWhitespace = CKEDITOR.dom.walker.whitespaces(),
2933 nodeInBetween;
2934
2935 gapRange.setStart( lastRange.endContainer, lastRange.endOffset );
2936 gapRange.setEnd( rng.startContainer, rng.startOffset );
2937
2938 nodeInBetween = walker.next();
2939
2940 while ( isWhitespace( nodeInBetween ) || rng.endContainer.equals( nodeInBetween ) ) {
2941 // We don't care about whitespaces, and range container. Also we skip the endContainer,
2942 // as it will also be provided by the iterator (as it visits it's opening tag).
2943 nodeInBetween = walker.next();
2944 }
2945
2946 // Simply, if anything has been found there's a content in between the two.
2947 isContinuation = !nodeInBetween;
2948 }
2949
2950 if ( isContinuation ) {
2951 // If last range ends, where the current range starts, then let's merge it.
2952 lastRange.setEnd( rng.endContainer, rng.endOffset );
2953 } else {
2954 // In other case just push cur range into the stack.
2955 ret.push( rng );
2956 }
2957
2958 return ret;
2959 }, [] );
2960 };
2789 2961
2790} )(); 2962} )();
2791 2963
@@ -2861,9 +3033,32 @@ CKEDITOR.POSITION_BEFORE_START = 3;
2861 */ 3033 */
2862CKEDITOR.POSITION_AFTER_END = 4; 3034CKEDITOR.POSITION_AFTER_END = 4;
2863 3035
3036/**
3037 * @readonly
3038 * @member CKEDITOR
3039 * @property {Number} [=1]
3040 */
2864CKEDITOR.ENLARGE_ELEMENT = 1; 3041CKEDITOR.ENLARGE_ELEMENT = 1;
3042
3043/**
3044 * @readonly
3045 * @member CKEDITOR
3046 * @property {Number} [=2]
3047 */
2865CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2; 3048CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2;
3049
3050/**
3051 * @readonly
3052 * @member CKEDITOR
3053 * @property {Number} [=3]
3054 */
2866CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3; 3055CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;
3056
3057/**
3058 * @readonly
3059 * @member CKEDITOR
3060 * @property {Number} [=4]
3061 */
2867CKEDITOR.ENLARGE_INLINE = 4; 3062CKEDITOR.ENLARGE_INLINE = 4;
2868 3063
2869// Check boundary types. 3064// Check boundary types.
diff --git a/sources/core/dom/rangelist.js b/sources/core/dom/rangelist.js
index d02fc03..15ccb88 100644
--- a/sources/core/dom/rangelist.js
+++ b/sources/core/dom/rangelist.js
@@ -1,5 +1,5 @@
1/** 1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license 3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */ 4 */
5 5
@@ -164,7 +164,7 @@
164 }; 164 };
165 165
166 // Update the specified range which has been mangled by previous insertion of 166 // Update the specified range which has been mangled by previous insertion of
167 // range bookmark nodes.(#3256) 167 // range bookmark nodes.(http://dev.ckeditor.com/ticket/3256)
168 function updateDirtyRange( bookmark, dirtyRange, checkEnd ) { 168 function updateDirtyRange( bookmark, dirtyRange, checkEnd ) {
169 var serializable = bookmark.serializable, 169 var serializable = bookmark.serializable,
170 container = dirtyRange[ checkEnd ? 'endContainer' : 'startContainer' ], 170 container = dirtyRange[ checkEnd ? 'endContainer' : 'startContainer' ],
diff --git a/sources/core/dom/text.js b/sources/core/dom/text.js
index c313259..e77a3d9 100644
--- a/sources/core/dom/text.js
+++ b/sources/core/dom/text.js
@@ -1,5 +1,5 @@
1/** 1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license 3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */ 4 */
5 5
@@ -106,7 +106,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.text.prototype, {
106 retval.insertAfter( this ); 106 retval.insertAfter( this );
107 } else { 107 } else {
108 // IE BUG: IE8+ does not update the childNodes array in DOM after splitText(), 108 // IE BUG: IE8+ does not update the childNodes array in DOM after splitText(),
109 // we need to make some DOM changes to make it update. (#3436) 109 // we need to make some DOM changes to make it update. (http://dev.ckeditor.com/ticket/3436)
110 var workaround = doc.createText( '' ); 110 var workaround = doc.createText( '' );
111 workaround.insertAfter( retval ); 111 workaround.insertAfter( retval );
112 workaround.remove(); 112 workaround.remove();
diff --git a/sources/core/dom/walker.js b/sources/core/dom/walker.js
index 746b406..8665909 100644
--- a/sources/core/dom/walker.js
+++ b/sources/core/dom/walker.js
@@ -1,5 +1,5 @@
1/** 1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license 3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */ 4 */
5 5
@@ -319,7 +319,7 @@
319 */ 319 */
320 CKEDITOR.dom.element.prototype.isBlockBoundary = function( customNodeNames ) { 320 CKEDITOR.dom.element.prototype.isBlockBoundary = function( customNodeNames ) {
321 // Whether element is in normal page flow. Floated or positioned elements are out of page flow. 321 // Whether element is in normal page flow. Floated or positioned elements are out of page flow.
322 // Don't consider floated or positioned formatting as block boundary, fall back to dtd check in that case. (#6297) 322 // Don't consider floated or positioned formatting as block boundary, fall back to dtd check in that case. (http://dev.ckeditor.com/ticket/6297)
323 var inPageFlow = this.getComputedStyle( 'float' ) == 'none' && !( this.getComputedStyle( 'position' ) in outOfFlowPositions ); 323 var inPageFlow = this.getComputedStyle( 'float' ) == 'none' && !( this.getComputedStyle( 'position' ) in outOfFlowPositions );
324 324
325 if ( inPageFlow && blockBoundaryDisplayMatch[ this.getComputedStyle( 'display' ) ] ) 325 if ( inPageFlow && blockBoundaryDisplayMatch[ this.getComputedStyle( 'display' ) ] )
@@ -388,7 +388,7 @@
388 return function( node ) { 388 return function( node ) {
389 var isWhitespace; 389 var isWhitespace;
390 if ( node && node.type == CKEDITOR.NODE_TEXT ) { 390 if ( node && node.type == CKEDITOR.NODE_TEXT ) {
391 // Whitespace, as well as the Filling Char Sequence text node used in Webkit. (#9384, #13816) 391 // Whitespace, as well as the Filling Char Sequence text node used in Webkit. (http://dev.ckeditor.com/ticket/9384, http://dev.ckeditor.com/ticket/13816)
392 isWhitespace = !CKEDITOR.tools.trim( node.getText() ) || 392 isWhitespace = !CKEDITOR.tools.trim( node.getText() ) ||
393 CKEDITOR.env.webkit && node.getText() == CKEDITOR.dom.selection.FILLING_CHAR_SEQUENCE; 393 CKEDITOR.env.webkit && node.getText() == CKEDITOR.dom.selection.FILLING_CHAR_SEQUENCE;
394 } 394 }
@@ -406,7 +406,7 @@
406 */ 406 */
407 CKEDITOR.dom.walker.invisible = function( isReject ) { 407 CKEDITOR.dom.walker.invisible = function( isReject ) {
408 var whitespace = CKEDITOR.dom.walker.whitespaces(), 408 var whitespace = CKEDITOR.dom.walker.whitespaces(),
409 // #12221 (Chrome) plus #11111 (Safari). 409 // http://dev.ckeditor.com/ticket/12221 (Chrome) plus http://dev.ckeditor.com/ticket/11111 (Safari).
410 offsetWidth0 = CKEDITOR.env.webkit ? 1 : 0; 410 offsetWidth0 = CKEDITOR.env.webkit ? 1 : 0;
411 411
412 return function( node ) { 412 return function( node ) {
@@ -422,7 +422,7 @@
422 // Nodes that take no spaces in wysiwyg: 422 // Nodes that take no spaces in wysiwyg:
423 // 1. White-spaces but not including NBSP. 423 // 1. White-spaces but not including NBSP.
424 // 2. Empty inline elements, e.g. <b></b>. 424 // 2. Empty inline elements, e.g. <b></b>.
425 // 3. <br> elements (bogus, surrounded by text) (#12423). 425 // 3. <br> elements (bogus, surrounded by text) (http://dev.ckeditor.com/ticket/12423).
426 invisible = node.$.offsetWidth <= offsetWidth0; 426 invisible = node.$.offsetWidth <= offsetWidth0;
427 } 427 }
428 428
@@ -636,7 +636,7 @@
636 * @returns {CKEDITOR.dom.node/Boolean} Bogus node or `false`. 636 * @returns {CKEDITOR.dom.node/Boolean} Bogus node or `false`.
637 */ 637 */
638 CKEDITOR.dom.element.prototype.getBogus = function() { 638 CKEDITOR.dom.element.prototype.getBogus = function() {
639 // Bogus are not always at the end, e.g. <p><a>text<br /></a></p> (#7070). 639 // Bogus are not always at the end, e.g. <p><a>text<br /></a></p> (http://dev.ckeditor.com/ticket/7070).
640 var tail = this; 640 var tail = this;
641 do { 641 do {
642 tail = tail.getPreviousSourceNode(); 642 tail = tail.getPreviousSourceNode();
diff --git a/sources/core/dom/window.js b/sources/core/dom/window.js
index edfeb84..ceeaeff 100644
--- a/sources/core/dom/window.js
+++ b/sources/core/dom/window.js
@@ -1,5 +1,5 @@
1/** 1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. 2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license 3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */ 4 */
5 5