aboutsummaryrefslogtreecommitdiff
path: root/sources/core
diff options
context:
space:
mode:
Diffstat (limited to 'sources/core')
-rw-r--r--sources/core/_bootstrap.js8
-rw-r--r--sources/core/ckeditor.js4
-rw-r--r--sources/core/ckeditor_base.js6
-rw-r--r--sources/core/ckeditor_basic.js2
-rw-r--r--sources/core/command.js4
-rw-r--r--sources/core/commanddefinition.js44
-rw-r--r--sources/core/config.js2
-rw-r--r--sources/core/creators/inline.js2
-rw-r--r--sources/core/creators/themedui.js32
-rw-r--r--sources/core/dataprocessor.js2
-rw-r--r--sources/core/dom.js2
-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
-rw-r--r--sources/core/dtd.js2
-rw-r--r--sources/core/editable.js267
-rw-r--r--sources/core/editor.js120
-rw-r--r--sources/core/editor_basic.js2
-rw-r--r--sources/core/env.js4
-rw-r--r--sources/core/event.js2
-rw-r--r--sources/core/eventInfo.js2
-rw-r--r--sources/core/filter.js159
-rw-r--r--sources/core/focusmanager.js12
-rw-r--r--sources/core/htmldataprocessor.js77
-rw-r--r--sources/core/htmlparser.js4
-rw-r--r--sources/core/htmlparser/basicwriter.js4
-rw-r--r--sources/core/htmlparser/cdata.js2
-rw-r--r--sources/core/htmlparser/comment.js2
-rw-r--r--sources/core/htmlparser/element.js44
-rw-r--r--sources/core/htmlparser/filter.js2
-rw-r--r--sources/core/htmlparser/fragment.js16
-rw-r--r--sources/core/htmlparser/node.js2
-rw-r--r--sources/core/htmlparser/text.js2
-rw-r--r--sources/core/keystrokehandler.js2
-rw-r--r--sources/core/lang.js8
-rw-r--r--sources/core/loader.js6
-rw-r--r--sources/core/log.js2
-rw-r--r--sources/core/plugindefinition.js2
-rw-r--r--sources/core/plugins.js2
-rw-r--r--sources/core/resourcemanager.js8
-rw-r--r--sources/core/scriptloader.js9
-rw-r--r--sources/core/selection.js446
-rw-r--r--sources/core/skin.js4
-rw-r--r--sources/core/style.js82
-rw-r--r--sources/core/template.js71
-rw-r--r--sources/core/tools.js701
-rw-r--r--sources/core/ui.js2
59 files changed, 2188 insertions, 506 deletions
diff --git a/sources/core/_bootstrap.js b/sources/core/_bootstrap.js
index 9fcbe25..cc7fb38 100644
--- a/sources/core/_bootstrap.js
+++ b/sources/core/_bootstrap.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
@@ -8,7 +8,7 @@
8 */ 8 */
9 9
10( function() { 10( function() {
11 // Disable HC detection in WebKit. (#5429) 11 // Disable HC detection in WebKit. (http://dev.ckeditor.com/ticket/5429)
12 if ( CKEDITOR.env.webkit ) 12 if ( CKEDITOR.env.webkit )
13 CKEDITOR.env.hc = false; 13 CKEDITOR.env.hc = false;
14 else { 14 else {
@@ -19,13 +19,13 @@
19 hcDetect.appendTo( CKEDITOR.document.getHead() ); 19 hcDetect.appendTo( CKEDITOR.document.getHead() );
20 20
21 // Update CKEDITOR.env. 21 // Update CKEDITOR.env.
22 // Catch exception needed sometimes for FF. (#4230) 22 // Catch exception needed sometimes for FF. (http://dev.ckeditor.com/ticket/4230)
23 try { 23 try {
24 var top = hcDetect.getComputedStyle( 'border-top-color' ), 24 var top = hcDetect.getComputedStyle( 'border-top-color' ),
25 right = hcDetect.getComputedStyle( 'border-right-color' ); 25 right = hcDetect.getComputedStyle( 'border-right-color' );
26 26
27 // We need to check if getComputedStyle returned any value, because on FF 27 // We need to check if getComputedStyle returned any value, because on FF
28 // it returnes empty string if CKEditor is loaded in hidden iframe. (#11121) 28 // it returnes empty string if CKEditor is loaded in hidden iframe. (http://dev.ckeditor.com/ticket/11121)
29 CKEDITOR.env.hc = !!( top && top == right ); 29 CKEDITOR.env.hc = !!( top && top == right );
30 } catch ( e ) { 30 } catch ( e ) {
31 CKEDITOR.env.hc = false; 31 CKEDITOR.env.hc = false;
diff --git a/sources/core/ckeditor.js b/sources/core/ckeditor.js
index 2b3e5cd..95dd67a 100644
--- a/sources/core/ckeditor.js
+++ b/sources/core/ckeditor.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
@@ -126,7 +126,7 @@ CKEDITOR.remove = function( editor ) {
126 }; 126 };
127 127
128 /** 128 /**
129 * Returns a string will all CSS rules passed to the {@link CKEDITOR#addCss} method. 129 * Returns a string with all CSS rules passed to the {@link CKEDITOR#addCss} method.
130 * 130 *
131 * @returns {String} A string containing CSS rules. 131 * @returns {String} A string containing CSS rules.
132 */ 132 */
diff --git a/sources/core/ckeditor_base.js b/sources/core/ckeditor_base.js
index 1e90d72..8c36b11 100644
--- a/sources/core/ckeditor_base.js
+++ b/sources/core/ckeditor_base.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
@@ -33,6 +33,10 @@ if ( !window.CKEDITOR ) {
33 * by the editor code, guaranteeing clean cache results when 33 * by the editor code, guaranteeing clean cache results when
34 * upgrading. 34 * upgrading.
35 * 35 *
36 * **Note:** There is [a known issue where "icons.png" does not include
37 * timestamp](http://dev.ckeditor.com/ticket/10685) and might get cached.
38 * We are working on having it fixed.
39 *
36 * alert( CKEDITOR.timestamp ); // e.g. '87dm' 40 * alert( CKEDITOR.timestamp ); // e.g. '87dm'
37 */ 41 */
38 timestamp: '', // %REMOVE_LINE% 42 timestamp: '', // %REMOVE_LINE%
diff --git a/sources/core/ckeditor_basic.js b/sources/core/ckeditor_basic.js
index 847d661..c07e4d9 100644
--- a/sources/core/ckeditor_basic.js
+++ b/sources/core/ckeditor_basic.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/command.js b/sources/core/command.js
index a0e07b5..128cea4 100644
--- a/sources/core/command.js
+++ b/sources/core/command.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
@@ -47,7 +47,7 @@ CKEDITOR.command = function( editor, commandDefinition ) {
47 if ( this.state == CKEDITOR.TRISTATE_DISABLED || !this.checkAllowed() ) 47 if ( this.state == CKEDITOR.TRISTATE_DISABLED || !this.checkAllowed() )
48 return false; 48 return false;
49 49
50 if ( this.editorFocus ) // Give editor focus if necessary (#4355). 50 if ( this.editorFocus ) // Give editor focus if necessary (http://dev.ckeditor.com/ticket/4355).
51 editor.focus(); 51 editor.focus();
52 52
53 if ( this.fire( 'exec' ) === false ) 53 if ( this.fire( 'exec' ) === false )
diff --git a/sources/core/commanddefinition.js b/sources/core/commanddefinition.js
index 68b2253..10a040f 100644
--- a/sources/core/commanddefinition.js
+++ b/sources/core/commanddefinition.js
@@ -1,10 +1,10 @@
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 * @fileOverview Defines the "virtual" {@link CKEDITOR.commandDefinition} class, 7 * @fileOverview Defines the "virtual" {@link CKEDITOR.commandDefinition} class
8 * which contains the defintion of a command. This file is for 8 * which contains the defintion of a command. This file is for
9 * documentation purposes only. 9 * documentation purposes only.
10 */ 10 */
@@ -27,14 +27,14 @@
27 * } ); 27 * } );
28 * 28 *
29 * @method exec 29 * @method exec
30 * @param {CKEDITOR.editor} editor The editor within which run the command. 30 * @param {CKEDITOR.editor} editor The editor within which to run the command.
31 * @param {Object} [data] Additional data to be used to execute the command. 31 * @param {Object} [data] Additional data to be used to execute the command.
32 * @returns {Boolean} Whether the command has been successfully executed. 32 * @returns {Boolean} Whether the command has been successfully executed.
33 * Defaults to `true`, if nothing is returned. 33 * Defaults to `true` if nothing is returned.
34 */ 34 */
35 35
36/** 36/**
37 * Whether the command need to be hooked into the redo/undo system. 37 * Whether the command needs to be hooked into the redo/undo system.
38 * 38 *
39 * editorInstance.addCommand( 'alertName', { 39 * editorInstance.addCommand( 'alertName', {
40 * exec: function( editor ) { 40 * exec: function( editor ) {
@@ -52,14 +52,18 @@
52 * command itself manually, and that the return value of this command is not to 52 * command itself manually, and that the return value of this command is not to
53 * be returned by the {@link #exec} function. 53 * be returned by the {@link #exec} function.
54 * 54 *
55 * editorInstance.addCommand( 'loadOptions', { 55 * editorInstance.addCommand( 'loadoptions', {
56 * exec: function( editor ) { 56 * exec: function( editor ) {
57 * var cmd = this;
57 * // Asynchronous operation below. 58 * // Asynchronous operation below.
58 * CKEDITOR.ajax.loadXml( 'data.xml', function() { 59 * CKEDITOR.ajax.loadXml( 'data.xml', function() {
59 * editor.fire( 'afterCommandExec' ); 60 * editor.fire( 'afterCommandExec', {
61 * name: 'loadoptions',
62 * command: cmd
63 * } );
60 * } ); 64 * } );
61 * }, 65 * },
62 * async: true // The command need some time to complete after exec function returns. 66 * async: true // The command needs some time to complete after the exec function returns.
63 * } ); 67 * } );
64 * 68 *
65 * @property {Boolean} [async=false] 69 * @property {Boolean} [async=false]
@@ -72,7 +76,7 @@
72 * exec: function( editor ) { 76 * exec: function( editor ) {
73 * // ... 77 * // ...
74 * }, 78 * },
75 * editorFocus: false // The command doesn't require focusing the editing document. 79 * editorFocus: false // The command does not require focusing the editing document.
76 * } ); 80 * } );
77 * 81 *
78 * See also {@link CKEDITOR.command#editorFocus}. 82 * See also {@link CKEDITOR.command#editorFocus}.
@@ -88,14 +92,14 @@
88 * exec: function( editor ) { 92 * exec: function( editor ) {
89 * // ... 93 * // ...
90 * }, 94 * },
91 * startDisabled: true // Command is unavailable until selection is inside a link. 95 * startDisabled: true // The command is unavailable until the selection is inside a link.
92 * } ); 96 * } );
93 * 97 *
94 * @property {Boolean} [startDisabled=false] 98 * @property {Boolean} [startDisabled=false]
95 */ 99 */
96 100
97/** 101/**
98 * Indicates that this command is sensible to the selection context. 102 * Indicates that this command is sensitive to the selection context.
99 * If `true`, the {@link CKEDITOR.command#method-refresh} method will be 103 * If `true`, the {@link CKEDITOR.command#method-refresh} method will be
100 * called for this command on selection changes, with a single parameter 104 * called for this command on selection changes, with a single parameter
101 * representing the current elements path. 105 * representing the current elements path.
@@ -104,10 +108,10 @@
104 */ 108 */
105 109
106/** 110/**
107 * Defined by command definition a function to determinate the command state, it will be invoked 111 * Defined by the command definition, a function to determine the command state. It will be invoked
108 * when editor has it's `states` or `selection` changed. 112 * when the editor has its `states` or `selection` changed.
109 * 113 *
110 * **Note:** The function provided must be calling {@link CKEDITOR.command#setState} in all circumstance, 114 * **Note:** The function provided must be calling {@link CKEDITOR.command#setState} in all circumstances
111 * if it is intended to update the command state. 115 * if it is intended to update the command state.
112 * 116 *
113 * @method refresh 117 * @method refresh
@@ -132,7 +136,7 @@
132 * exec: function( editor ) { 136 * exec: function( editor ) {
133 * // ... 137 * // ...
134 * }, 138 * },
135 * modes: { wysiwyg:1 } // Command is available in wysiwyg mode only. 139 * modes: { wysiwyg:1 } // The command is available in wysiwyg mode only.
136 * } ); 140 * } );
137 * 141 *
138 * See also {@link CKEDITOR.command#modes}. 142 * See also {@link CKEDITOR.command#modes}.
@@ -146,3 +150,13 @@
146 * @since 4.0 150 * @since 4.0
147 * @property {Boolean} [readOnly=false] 151 * @property {Boolean} [readOnly=false]
148 */ 152 */
153
154/**
155 * A property that should be set when a command has no keystroke assigned by {@link CKEDITOR.editor#setKeystroke}, but
156 * the keystroke is still supported. For example: `cut`, `copy` and `paste` commands are handled that way.
157 * This property is used when displaying keystroke information in tooltips and context menus. It is used by
158 * {@link CKEDITOR.editor#getCommandKeystroke}.
159 *
160 * @since 4.6.0
161 * @property {Number} fakeKeystroke
162 */
diff --git a/sources/core/config.js b/sources/core/config.js
index 4ce98d7..f0f4aec 100644
--- a/sources/core/config.js
+++ b/sources/core/config.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/creators/inline.js b/sources/core/creators/inline.js
index a1a653c..adb402f 100644
--- a/sources/core/creators/inline.js
+++ b/sources/core/creators/inline.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/creators/themedui.js b/sources/core/creators/themedui.js
index c87d079..4e7a93e 100644
--- a/sources/core/creators/themedui.js
+++ b/sources/core/creators/themedui.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
@@ -20,7 +20,7 @@ CKEDITOR.replaceClass = 'ckeditor';
20 * Replaces a `<textarea>` or a DOM element (`<div>`) with a CKEditor 20 * Replaces a `<textarea>` or a DOM element (`<div>`) with a CKEditor
21 * instance. For textareas, the initial value in the editor will be the 21 * instance. For textareas, the initial value in the editor will be the
22 * textarea value. For DOM elements, their `innerHTML` will be used 22 * textarea value. For DOM elements, their `innerHTML` will be used
23 * instead. We recommend using `<textarea>` and `<div>` elements only. 23 * instead. It is recommended to use `<textarea>` and `<div>` elements only.
24 * 24 *
25 * <textarea id="myfield" name="myfield"></textarea> 25 * <textarea id="myfield" name="myfield"></textarea>
26 * ... 26 * ...
@@ -79,10 +79,10 @@ CKEDITOR.replaceClass = 'ckeditor';
79 * // Replace all <textarea class="myClassName"> elements in the page. 79 * // Replace all <textarea class="myClassName"> elements in the page.
80 * CKEDITOR.replaceAll( 'myClassName' ); 80 * CKEDITOR.replaceAll( 'myClassName' );
81 * 81 *
82 * // Selectively replace <textarea> elements, based on custom assertions. 82 * // Selectively replace <textarea> elements, based on a custom evaluation function.
83 * CKEDITOR.replaceAll( function( textarea, config ) { 83 * CKEDITOR.replaceAll( function( textarea, config ) {
84 * // An assertion function that needs to be evaluated for the <textarea> 84 * // A function that needs to be evaluated for the <textarea>
85 * // to be replaced. It must explicitely return "false" to ignore a 85 * // to be replaced. It must explicitly return "false" to ignore a
86 * // specific <textarea>. 86 * // specific <textarea>.
87 * // You can also customize the editor instance by having the function 87 * // You can also customize the editor instance by having the function
88 * // modify the "config" parameter. 88 * // modify the "config" parameter.
@@ -109,7 +109,7 @@ CKEDITOR.replaceClass = 'ckeditor';
109 * </html> 109 * </html>
110 * 110 *
111 * @param {String} [className] The `<textarea>` class name. 111 * @param {String} [className] The `<textarea>` class name.
112 * @param {Function} [function] An assertion function that must return `true` for a `<textarea>` 112 * @param {Function} [evaluator] An evaluation function that must return `true` for a `<textarea>`
113 * to be replaced with the editor. If the function returns `false`, the `<textarea>` element 113 * to be replaced with the editor. If the function returns `false`, the `<textarea>` element
114 * will not be replaced. 114 * will not be replaced.
115 */ 115 */
@@ -133,7 +133,7 @@ CKEDITOR.replaceClass = 'ckeditor';
133 if ( !classRegex.test( textarea.className ) ) 133 if ( !classRegex.test( textarea.className ) )
134 continue; 134 continue;
135 } else if ( typeof arguments[ 0 ] == 'function' ) { 135 } else if ( typeof arguments[ 0 ] == 'function' ) {
136 // An assertion function could be passed as the function parameter. 136 // An evaluation function could be passed as the function parameter.
137 // It must explicitly return "false" to ignore a specific <textarea>. 137 // It must explicitly return "false" to ignore a specific <textarea>.
138 config = {}; 138 config = {};
139 if ( arguments[ 0 ]( textarea, config ) === false ) 139 if ( arguments[ 0 ]( textarea, config ) === false )
@@ -280,10 +280,10 @@ CKEDITOR.replaceClass = 'ckeditor';
280 outer = container; 280 outer = container;
281 } 281 }
282 282
283 // Set as border box width. (#5353) 283 // Set as border box width. (http://dev.ckeditor.com/ticket/5353)
284 outer.setSize( 'width', width, true ); 284 outer.setSize( 'width', width, true );
285 285
286 // WebKit needs to refresh the iframe size to avoid rendering issues. (1/2) (#8348) 286 // WebKit needs to refresh the iframe size to avoid rendering issues. (1/2) (http://dev.ckeditor.com/ticket/8348)
287 contentsFrame && ( contentsFrame.style.width = '1%' ); 287 contentsFrame && ( contentsFrame.style.width = '1%' );
288 288
289 // Get the height delta between the outer table and the content area. 289 // Get the height delta between the outer table and the content area.
@@ -295,7 +295,7 @@ CKEDITOR.replaceClass = 'ckeditor';
295 295
296 contents.setStyle( 'height', resultContentsHeight + 'px' ); 296 contents.setStyle( 'height', resultContentsHeight + 'px' );
297 297
298 // WebKit needs to refresh the iframe size to avoid rendering issues. (2/2) (#8348) 298 // WebKit needs to refresh the iframe size to avoid rendering issues. (2/2) (http://dev.ckeditor.com/ticket/8348)
299 contentsFrame && ( contentsFrame.style.width = '100%' ); 299 contentsFrame && ( contentsFrame.style.width = '100%' );
300 300
301 // Emit a resize event. 301 // Emit a resize event.
@@ -309,8 +309,8 @@ CKEDITOR.replaceClass = 'ckeditor';
309 309
310 /** 310 /**
311 * Gets the element that can be used to check the editor size. This method 311 * Gets the element that can be used to check the editor size. This method
312 * is mainly used by the `resize` plugin, which adds a UI handle that can be used 312 * is mainly used by the [Editor Resize](http://ckeditor.com/addon/resize) plugin, which adds
313 * to resize the editor. 313 * a UI handle that can be used to resize the editor.
314 * 314 *
315 * @param {Boolean} forContents Whether to return the "contents" part of the theme instead of the container. 315 * @param {Boolean} forContents Whether to return the "contents" part of the theme instead of the container.
316 * @returns {CKEDITOR.dom.element} The resizable element. 316 * @returns {CKEDITOR.dom.element} The resizable element.
@@ -337,7 +337,7 @@ CKEDITOR.replaceClass = 'ckeditor';
337 // replacement will be done later in the editor creation lifecycle. 337 // replacement will be done later in the editor creation lifecycle.
338 element.setStyle( 'visibility', 'hidden' ); 338 element.setStyle( 'visibility', 'hidden' );
339 339
340 // #8031 Remember if textarea was required and remove the attribute. 340 // http://dev.ckeditor.com/ticket/8031 Remember if textarea was required and remove the attribute.
341 editor._.required = element.hasAttribute( 'required' ); 341 editor._.required = element.hasAttribute( 'required' );
342 element.removeAttribute( 'required' ); 342 element.removeAttribute( 'required' );
343 } 343 }
@@ -422,7 +422,7 @@ CKEDITOR.replaceClass = 'ckeditor';
422 topHtml: topHtml ? '<span id="' + editor.ui.spaceId( 'top' ) + '" class="cke_top cke_reset_all" role="presentation" style="height:auto">' + topHtml + '</span>' : '', 422 topHtml: topHtml ? '<span id="' + editor.ui.spaceId( 'top' ) + '" class="cke_top cke_reset_all" role="presentation" style="height:auto">' + topHtml + '</span>' : '',
423 contentId: editor.ui.spaceId( 'contents' ), 423 contentId: editor.ui.spaceId( 'contents' ),
424 bottomHtml: bottomHtml ? '<span id="' + editor.ui.spaceId( 'bottom' ) + '" class="cke_bottom cke_reset_all" role="presentation">' + bottomHtml + '</span>' : '', 424 bottomHtml: bottomHtml ? '<span id="' + editor.ui.spaceId( 'bottom' ) + '" class="cke_bottom cke_reset_all" role="presentation">' + bottomHtml + '</span>' : '',
425 outerEl: CKEDITOR.env.ie ? 'span' : 'div' // #9571 425 outerEl: CKEDITOR.env.ie ? 'span' : 'div' // http://dev.ckeditor.com/ticket/9571
426 } ) ); 426 } ) );
427 427
428 if ( elementMode == CKEDITOR.ELEMENT_MODE_REPLACE ) { 428 if ( elementMode == CKEDITOR.ELEMENT_MODE_REPLACE ) {
@@ -451,7 +451,7 @@ CKEDITOR.replaceClass = 'ckeditor';
451 // Disable browser context menu for editor's chrome. 451 // Disable browser context menu for editor's chrome.
452 container.disableContextMenu(); 452 container.disableContextMenu();
453 453
454 // Redirect the focus into editor for webkit. (#5713) 454 // Redirect the focus into editor for webkit. (http://dev.ckeditor.com/ticket/5713)
455 CKEDITOR.env.webkit && container.on( 'focus', function() { 455 CKEDITOR.env.webkit && container.on( 'focus', function() {
456 editor.focus(); 456 editor.focus();
457 } ); 457 } );
@@ -467,7 +467,7 @@ CKEDITOR.replaceClass = 'ckeditor';
467 467
468/** 468/**
469 * The current editing mode. An editing mode basically provides 469 * The current editing mode. An editing mode basically provides
470 * different ways of editing or viewing the contents. 470 * different ways of editing or viewing the editor content.
471 * 471 *
472 * alert( CKEDITOR.instances.editor1.mode ); // (e.g.) 'wysiwyg' 472 * alert( CKEDITOR.instances.editor1.mode ); // (e.g.) 'wysiwyg'
473 * 473 *
diff --git a/sources/core/dataprocessor.js b/sources/core/dataprocessor.js
index ebc4d1c..c42eab4 100644
--- a/sources/core/dataprocessor.js
+++ b/sources/core/dataprocessor.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.js b/sources/core/dom.js
index a806a74..84aa21d 100644
--- a/sources/core/dom.js
+++ b/sources/core/dom.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/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
diff --git a/sources/core/dtd.js b/sources/core/dtd.js
index d6dc5a6..6059d48 100644
--- a/sources/core/dtd.js
+++ b/sources/core/dtd.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/editable.js b/sources/core/editable.js
index b9b0270..6b3fa9f 100644
--- a/sources/core/editable.js
+++ b/sources/core/editable.js
@@ -1,9 +1,12 @@
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( function() { 6( function() {
7 var isNotWhitespace, isNotBookmark, isEmpty, isBogus, emptyParagraphRegexp,
8 insert, fixTableAfterContentsDeletion, fixListAfterContentsDelete, getHtmlFromRangeHelpers, extractHtmlFromRangeHelpers;
9
7 /** 10 /**
8 * Editable class which provides all editing related activities by 11 * Editable class which provides all editing related activities by
9 * the `contenteditable` element, dynamically get attached to editor instance. 12 * the `contenteditable` element, dynamically get attached to editor instance.
@@ -71,17 +74,36 @@
71 } 74 }
72 } 75 }
73 76
74 // [IE] Use instead "setActive" method to focus the editable if it belongs to 77 // [Edge] Starting from EdgeHTML 14.14393, it does not support `setActive`. We need to use focus which
75 // the host page document, to avoid bringing an unexpected scroll. 78 // causes unexpected scroll. Store scrollTop value so it can be restored after focusing editor.
79 // Scroll only happens if the editor is focused for the first time. (http://dev.ckeditor.com/ticket/14825)
80 if ( CKEDITOR.env.edge && CKEDITOR.env.version > 14 && !this.hasFocus && this.getDocument().equals( CKEDITOR.document ) ) {
81 this.editor._.previousScrollTop = this.$.scrollTop;
82 }
83
84 // [IE] Use instead "setActive" method to focus the editable if it belongs to the host page document,
85 // to avoid bringing an unexpected scroll.
76 try { 86 try {
77 this.$[ CKEDITOR.env.ie && this.getDocument().equals( CKEDITOR.document ) ? 'setActive' : 'focus' ](); 87 if ( CKEDITOR.env.ie && !( CKEDITOR.env.edge && CKEDITOR.env.version > 14 ) && this.getDocument().equals( CKEDITOR.document ) ) {
88 this.$.setActive();
89 } else {
90 // We have no control over exactly what happens when the native `focus` method is called,
91 // so save the scroll position and restore it later.
92 if ( CKEDITOR.env.chrome ) {
93 var scrollPos = this.$.scrollTop;
94 this.$.focus();
95 this.$.scrollTop = scrollPos;
96 } else {
97 this.$.focus();
98 }
99 }
78 } catch ( e ) { 100 } catch ( e ) {
79 // IE throws unspecified error when focusing editable after closing dialog opened on nested editable. 101 // IE throws unspecified error when focusing editable after closing dialog opened on nested editable.
80 if ( !CKEDITOR.env.ie ) 102 if ( !CKEDITOR.env.ie )
81 throw e; 103 throw e;
82 } 104 }
83 105
84 // Remedy if Safari doens't applies focus properly. (#279) 106 // Remedy if Safari doens't applies focus properly. (http://dev.ckeditor.com/ticket/279)
85 if ( CKEDITOR.env.safari && !this.isInline() ) { 107 if ( CKEDITOR.env.safari && !this.isInline() ) {
86 active = CKEDITOR.document.getActive(); 108 active = CKEDITOR.document.getActive();
87 if ( !active.equals( this.getWindow().getFrame() ) ) 109 if ( !active.equals( this.getWindow().getFrame() ) )
@@ -103,7 +125,7 @@
103 125
104 // The "focusin/focusout" events bubbled, e.g. If there are elements with layout 126 // The "focusin/focusout" events bubbled, e.g. If there are elements with layout
105 // they fire this event when clicking in to edit them but it must be ignored 127 // they fire this event when clicking in to edit them but it must be ignored
106 // to allow edit their contents. (#4682) 128 // to allow edit their contents. (http://dev.ckeditor.com/ticket/4682)
107 fn = isNotBubbling( fn, this ); 129 fn = isNotBubbling( fn, this );
108 args[ 0 ] = name; 130 args[ 0 ] = name;
109 args[ 1 ] = fn; 131 args[ 1 ] = fn;
@@ -238,7 +260,7 @@
238 * @param {String} text 260 * @param {String} text
239 */ 261 */
240 insertText: function( text ) { 262 insertText: function( text ) {
241 // Focus the editor before calling transformPlainTextToHtml. (#12726) 263 // Focus the editor before calling transformPlainTextToHtml. (http://dev.ckeditor.com/ticket/12726)
242 this.editor.focus(); 264 this.editor.focus();
243 this.insertHtml( this.transformPlainTextToHtml( text ), 'text' ); 265 this.insertHtml( this.transformPlainTextToHtml( text ), 'text' );
244 }, 266 },
@@ -336,7 +358,7 @@
336 insertElement: function( element, range ) { 358 insertElement: function( element, range ) {
337 var editor = this.editor; 359 var editor = this.editor;
338 360
339 // Prepare for the insertion. For example - focus editor (#11848). 361 // Prepare for the insertion. For example - focus editor (http://dev.ckeditor.com/ticket/11848).
340 editor.focus(); 362 editor.focus();
341 editor.fire( 'saveSnapshot' ); 363 editor.fire( 'saveSnapshot' );
342 364
@@ -349,12 +371,12 @@
349 range = selection.getRanges()[ 0 ]; 371 range = selection.getRanges()[ 0 ];
350 } 372 }
351 373
352 // Insert element into first range only and ignore the rest (#11183). 374 // Insert element into first range only and ignore the rest (http://dev.ckeditor.com/ticket/11183).
353 if ( this.insertElementIntoRange( element, range ) ) { 375 if ( this.insertElementIntoRange( element, range ) ) {
354 range.moveToPosition( element, CKEDITOR.POSITION_AFTER_END ); 376 range.moveToPosition( element, CKEDITOR.POSITION_AFTER_END );
355 377
356 // If we're inserting a block element, the new cursor position must be 378 // If we're inserting a block element, the new cursor position must be
357 // optimized. (#3100,#5436,#8950) 379 // optimized. (http://dev.ckeditor.com/ticket/3100,http://dev.ckeditor.com/ticket/5436,http://dev.ckeditor.com/ticket/8950)
358 if ( isBlock ) { 380 if ( isBlock ) {
359 // Find next, meaningful element. 381 // Find next, meaningful element.
360 var next = element.getNext( function( node ) { 382 var next = element.getNext( function( node ) {
@@ -415,12 +437,19 @@
415 // Remove the original contents, merge split nodes. 437 // Remove the original contents, merge split nodes.
416 range.deleteContents( 1 ); 438 range.deleteContents( 1 );
417 439
418 // If range is placed in inermediate element (not td or th), we need to do three things: 440 if ( range.startContainer.type == CKEDITOR.NODE_ELEMENT ) {
419 // * fill emptied <td/th>s with if browser needs them, 441 // If range is placed in intermediate element (not td or th), we need to do three things:
420 // * remove empty text nodes so IE8 won't crash (http://dev.ckeditor.com/ticket/11183#comment:8), 442 // * fill emptied <td/th>s with if browser needs them,
421 // * fix structure and move range into the <td/th> element. 443 // * remove empty text nodes so IE8 won't crash
422 if ( range.startContainer.type == CKEDITOR.NODE_ELEMENT && range.startContainer.is( { tr: 1, table: 1, tbody: 1, thead: 1, tfoot: 1 } ) ) 444 // (http://dev.ckeditor.com/ticket/11183#comment:8),
423 fixTableAfterContentsDeletion( range ); 445 // * fix structure and move range into the <td/th> element.
446 if ( range.startContainer.is( { tr: 1, table: 1, tbody: 1, thead: 1, tfoot: 1 } ) ) {
447 fixTableAfterContentsDeletion( range );
448 } else if ( range.startContainer.is( CKEDITOR.dtd.$list ) ) {
449 // Similarly there's a need for lists.
450 fixListAfterContentsDelete( range );
451 }
452 }
424 453
425 // If we're inserting a block at dtd-violated position, split 454 // If we're inserting a block at dtd-violated position, split
426 // the parent blocks until we reach blockLimit. 455 // the parent blocks until we reach blockLimit.
@@ -435,7 +464,7 @@
435 range.splitElement( current ); 464 range.splitElement( current );
436 465
437 // If we're in an empty block which indicate a new paragraph, 466 // If we're in an empty block which indicate a new paragraph,
438 // simply replace it with the inserting block.(#3664) 467 // simply replace it with the inserting block.(http://dev.ckeditor.com/ticket/3664)
439 else if ( range.checkStartOfBlock() && range.checkEndOfBlock() ) { 468 else if ( range.checkStartOfBlock() && range.checkEndOfBlock() ) {
440 range.setStartBefore( current ); 469 range.setStartBefore( current );
441 range.collapse( true ); 470 range.collapse( true );
@@ -749,7 +778,7 @@
749 range.checkEndOfBlock() && 778 range.checkEndOfBlock() &&
750 path.block && 779 path.block &&
751 !range.root.equals( path.block ) && 780 !range.root.equals( path.block ) &&
752 // Do not remove a block with bookmarks. (#13465) 781 // Do not remove a block with bookmarks. (http://dev.ckeditor.com/ticket/13465)
753 !hasBookmarks( path.block ) ) { 782 !hasBookmarks( path.block ) ) {
754 range.moveToPosition( path.block, CKEDITOR.POSITION_BEFORE_START ); 783 range.moveToPosition( path.block, CKEDITOR.POSITION_BEFORE_START );
755 path.block.remove(); 784 path.block.remove();
@@ -811,7 +840,7 @@
811 840
812 // IE considers control-type element as separate 841 // IE considers control-type element as separate
813 // focus host when selected, avoid destroying the 842 // focus host when selected, avoid destroying the
814 // selection in such case. (#5812) (#8949) 843 // selection in such case. (http://dev.ckeditor.com/ticket/5812) (http://dev.ckeditor.com/ticket/8949)
815 if ( ieSel && ieSel.type == 'Control' ) 844 if ( ieSel && ieSel.type == 'Control' )
816 return; 845 return;
817 846
@@ -862,6 +891,30 @@
862 this.hasFocus = true; 891 this.hasFocus = true;
863 }, null, null, -1 ); 892 }, null, null, -1 );
864 893
894 if ( CKEDITOR.env.webkit ) {
895 // [WebKit] Save scrollTop value so it can be used when restoring locked selection. (http://dev.ckeditor.com/ticket/14659)
896 this.on( 'scroll', function() {
897 editor._.previousScrollTop = editor.editable().$.scrollTop;
898 }, null, null, -1 );
899 }
900
901 // [Edge] This is the other part of the workaround for Edge which restores saved
902 // scrollTop value and removes listener which is not needed anymore. (http://dev.ckeditor.com/ticket/14825)
903 if ( CKEDITOR.env.edge && CKEDITOR.env.version > 14 ) {
904
905 var fixScrollOnFocus = function() {
906 var editable = editor.editable();
907
908 if ( editor._.previousScrollTop != null && editable.getDocument().equals( CKEDITOR.document ) ) {
909 editable.$.scrollTop = editor._.previousScrollTop;
910 editor._.previousScrollTop = null;
911 this.removeListener( 'scroll', fixScrollOnFocus );
912 }
913 };
914
915 this.on( 'scroll', fixScrollOnFocus );
916 }
917
865 // Register to focus manager. 918 // Register to focus manager.
866 editor.focusManager.add( this ); 919 editor.focusManager.add( this );
867 920
@@ -902,12 +955,16 @@
902 // Create the content stylesheet for this document. 955 // Create the content stylesheet for this document.
903 var styles = CKEDITOR.getCss(); 956 var styles = CKEDITOR.getCss();
904 if ( styles ) { 957 if ( styles ) {
905 var head = doc.getHead(); 958 var head = doc.getHead(),
906 if ( !head.getCustomData( 'stylesheet' ) ) { 959 stylesElement = head.getCustomData( 'stylesheet' );
960
961 if ( !stylesElement ) {
907 var sheet = doc.appendStyleText( styles ); 962 var sheet = doc.appendStyleText( styles );
908 sheet = new CKEDITOR.dom.element( sheet.ownerNode || sheet.owningElement ); 963 sheet = new CKEDITOR.dom.element( sheet.ownerNode || sheet.owningElement );
909 head.setCustomData( 'stylesheet', sheet ); 964 head.setCustomData( 'stylesheet', sheet );
910 sheet.data( 'cke-temp', 1 ); 965 sheet.data( 'cke-temp', 1 );
966 } else if ( styles != stylesElement.getText() ) {
967 CKEDITOR.env.ie && CKEDITOR.env.version < 9 ? stylesElement.$.styleSheet.cssText = styles : stylesElement.setText( styles );
911 } 968 }
912 } 969 }
913 970
@@ -918,7 +975,7 @@
918 // Pass this configuration to styles system. 975 // Pass this configuration to styles system.
919 this.setCustomData( 'cke_includeReadonly', !editor.config.disableReadonlyStyling ); 976 this.setCustomData( 'cke_includeReadonly', !editor.config.disableReadonlyStyling );
920 977
921 // Prevent the browser opening read-only links. (#6032 & #10912) 978 // Prevent the browser opening read-only links. (http://dev.ckeditor.com/ticket/6032 & http://dev.ckeditor.com/ticket/10912)
922 this.attachListener( this, 'click', function( evt ) { 979 this.attachListener( this, 'click', function( evt ) {
923 evt = evt.data; 980 evt = evt.data;
924 981
@@ -931,7 +988,7 @@
931 var backspaceOrDelete = { 8: 1, 46: 1 }; 988 var backspaceOrDelete = { 8: 1, 46: 1 };
932 989
933 // Override keystrokes which should have deletion behavior 990 // Override keystrokes which should have deletion behavior
934 // on fully selected element . (#4047) (#7645) 991 // on fully selected element . (http://dev.ckeditor.com/ticket/4047) (http://dev.ckeditor.com/ticket/7645)
935 this.attachListener( editor, 'key', function( evt ) { 992 this.attachListener( editor, 'key', function( evt ) {
936 if ( editor.readOnly ) 993 if ( editor.readOnly )
937 return true; 994 return true;
@@ -941,10 +998,15 @@
941 var keyCode = evt.data.domEvent.getKey(), 998 var keyCode = evt.data.domEvent.getKey(),
942 isHandled; 999 isHandled;
943 1000
1001 // Prevent of reading path of empty range (http://dev.ckeditor.com/ticket/13096, #457).
1002 var sel = editor.getSelection();
1003 if ( sel.getRanges().length === 0 ) {
1004 return;
1005 }
1006
944 // Backspace OR Delete. 1007 // Backspace OR Delete.
945 if ( keyCode in backspaceOrDelete ) { 1008 if ( keyCode in backspaceOrDelete ) {
946 var sel = editor.getSelection(), 1009 var selected,
947 selected,
948 range = sel.getRanges()[ 0 ], 1010 range = sel.getRanges()[ 0 ],
949 path = range.startPath(), 1011 path = range.startPath(),
950 block, 1012 block,
@@ -952,16 +1014,17 @@
952 next, 1014 next,
953 rtl = keyCode == 8; 1015 rtl = keyCode == 8;
954 1016
1017
955 if ( 1018 if (
956 // [IE<11] Remove selected image/anchor/etc here to avoid going back in history. (#10055) 1019 // [IE<11] Remove selected image/anchor/etc here to avoid going back in history. (http://dev.ckeditor.com/ticket/10055)
957 ( CKEDITOR.env.ie && CKEDITOR.env.version < 11 && ( selected = sel.getSelectedElement() ) ) || 1020 ( CKEDITOR.env.ie && CKEDITOR.env.version < 11 && ( selected = sel.getSelectedElement() ) ) ||
958 // Remove the entire list/table on fully selected content. (#7645) 1021 // Remove the entire list/table on fully selected content. (http://dev.ckeditor.com/ticket/7645)
959 ( selected = getSelectedTableList( sel ) ) ) { 1022 ( selected = getSelectedTableList( sel ) ) ) {
960 // Make undo snapshot. 1023 // Make undo snapshot.
961 editor.fire( 'saveSnapshot' ); 1024 editor.fire( 'saveSnapshot' );
962 1025
963 // Delete any element that 'hasLayout' (e.g. hr,table) in IE8 will 1026 // Delete any element that 'hasLayout' (e.g. hr,table) in IE8 will
964 // break up the selection, safely manage it here. (#4795) 1027 // break up the selection, safely manage it here. (http://dev.ckeditor.com/ticket/4795)
965 range.moveToPosition( selected, CKEDITOR.POSITION_BEFORE_START ); 1028 range.moveToPosition( selected, CKEDITOR.POSITION_BEFORE_START );
966 // Remove the control manually. 1029 // Remove the control manually.
967 selected.remove(); 1030 selected.remove();
@@ -971,7 +1034,7 @@
971 1034
972 isHandled = 1; 1035 isHandled = 1;
973 } else if ( range.collapsed ) { 1036 } else if ( range.collapsed ) {
974 // Handle the following special cases: (#6217) 1037 // Handle the following special cases: (http://dev.ckeditor.com/ticket/6217)
975 // 1. Del/Backspace key before/after table; 1038 // 1. Del/Backspace key before/after table;
976 // 2. Backspace Key after start of table. 1039 // 2. Backspace Key after start of table.
977 if ( ( block = path.block ) && 1040 if ( ( block = path.block ) &&
@@ -1046,28 +1109,28 @@
1046 editor.fire( 'doubleclick', data ); 1109 editor.fire( 'doubleclick', data );
1047 } ); 1110 } );
1048 1111
1049 // Prevent automatic submission in IE #6336 1112 // Prevent automatic submission in IE http://dev.ckeditor.com/ticket/6336
1050 CKEDITOR.env.ie && this.attachListener( this, 'click', blockInputClick ); 1113 CKEDITOR.env.ie && this.attachListener( this, 'click', blockInputClick );
1051 1114
1052 // Gecko/Webkit need some help when selecting control type elements. (#3448) 1115 // Gecko/Webkit need some help when selecting control type elements. (http://dev.ckeditor.com/ticket/3448)
1053 // We apply same behavior for IE Edge. (#13386) 1116 // We apply same behavior for IE Edge. (http://dev.ckeditor.com/ticket/13386)
1054 if ( !CKEDITOR.env.ie || CKEDITOR.env.edge ) { 1117 if ( !CKEDITOR.env.ie || CKEDITOR.env.edge ) {
1055 this.attachListener( this, 'mousedown', function( ev ) { 1118 this.attachListener( this, 'mousedown', function( ev ) {
1056 var control = ev.data.getTarget(); 1119 var control = ev.data.getTarget();
1057 // #11727. Note: htmlDP assures that input/textarea/select have contenteditable=false 1120 // http://dev.ckeditor.com/ticket/11727. Note: htmlDP assures that input/textarea/select have contenteditable=false
1058 // attributes. However, they also have data-cke-editable attribute, so isReadOnly() returns false, 1121 // attributes. However, they also have data-cke-editable attribute, so isReadOnly() returns false,
1059 // and therefore those elements are correctly selected by this code. 1122 // and therefore those elements are correctly selected by this code.
1060 if ( control.is( 'img', 'hr', 'input', 'textarea', 'select' ) && !control.isReadOnly() ) { 1123 if ( control.is( 'img', 'hr', 'input', 'textarea', 'select' ) && !control.isReadOnly() ) {
1061 editor.getSelection().selectElement( control ); 1124 editor.getSelection().selectElement( control );
1062 1125
1063 // Prevent focus from stealing from the editable. (#9515) 1126 // Prevent focus from stealing from the editable. (http://dev.ckeditor.com/ticket/9515)
1064 if ( control.is( 'input', 'textarea', 'select' ) ) 1127 if ( control.is( 'input', 'textarea', 'select' ) )
1065 ev.data.preventDefault(); 1128 ev.data.preventDefault();
1066 } 1129 }
1067 } ); 1130 } );
1068 } 1131 }
1069 1132
1070 // For some reason, after click event is done, IE Edge loses focus on the selected element. (#13386) 1133 // For some reason, after click event is done, IE Edge loses focus on the selected element. (http://dev.ckeditor.com/ticket/13386)
1071 if ( CKEDITOR.env.edge ) { 1134 if ( CKEDITOR.env.edge ) {
1072 this.attachListener( this, 'mouseup', function( ev ) { 1135 this.attachListener( this, 'mouseup', function( ev ) {
1073 var selectedElement = ev.data.getTarget(); 1136 var selectedElement = ev.data.getTarget();
@@ -1078,7 +1141,7 @@
1078 } 1141 }
1079 1142
1080 // Prevent right click from selecting an empty block even 1143 // Prevent right click from selecting an empty block even
1081 // when selection is anchored inside it. (#5845) 1144 // when selection is anchored inside it. (http://dev.ckeditor.com/ticket/5845)
1082 if ( CKEDITOR.env.gecko ) { 1145 if ( CKEDITOR.env.gecko ) {
1083 this.attachListener( this, 'mouseup', function( ev ) { 1146 this.attachListener( this, 'mouseup', function( ev ) {
1084 if ( ev.data.$.button == 2 ) { 1147 if ( ev.data.$.button == 2 ) {
@@ -1109,7 +1172,7 @@
1109 } 1172 }
1110 1173
1111 // Prevent Webkit/Blink from going rogue when joining 1174 // Prevent Webkit/Blink from going rogue when joining
1112 // blocks on BACKSPACE/DEL (#11861,#9998). 1175 // blocks on BACKSPACE/DEL (http://dev.ckeditor.com/ticket/11861,http://dev.ckeditor.com/ticket/9998).
1113 if ( CKEDITOR.env.webkit ) { 1176 if ( CKEDITOR.env.webkit ) {
1114 this.attachListener( editor, 'key', function( evt ) { 1177 this.attachListener( editor, 'key', function( evt ) {
1115 if ( editor.readOnly ) { 1178 if ( editor.readOnly ) {
@@ -1123,8 +1186,14 @@
1123 if ( !( key in backspaceOrDelete ) ) 1186 if ( !( key in backspaceOrDelete ) )
1124 return; 1187 return;
1125 1188
1189 // Prevent of reading path of empty range (http://dev.ckeditor.com/ticket/13096, #457).
1190 var sel = editor.getSelection();
1191 if ( sel.getRanges().length === 0 ) {
1192 return;
1193 }
1194
1126 var backspace = key == 8, 1195 var backspace = key == 8,
1127 range = editor.getSelection().getRanges()[ 0 ], 1196 range = sel.getRanges()[ 0 ],
1128 startPath = range.startPath(); 1197 startPath = range.startPath();
1129 1198
1130 if ( range.collapsed ) { 1199 if ( range.collapsed ) {
@@ -1135,7 +1204,7 @@
1135 return; 1204 return;
1136 } 1205 }
1137 1206
1138 // Scroll to the new position of the caret (#11960). 1207 // Scroll to the new position of the caret (http://dev.ckeditor.com/ticket/11960).
1139 editor.getSelection().scrollIntoView(); 1208 editor.getSelection().scrollIntoView();
1140 editor.fire( 'saveSnapshot' ); 1209 editor.fire( 'saveSnapshot' );
1141 1210
@@ -1190,8 +1259,9 @@
1190 * 1259 *
1191 * @method editable 1260 * @method editable
1192 * @member CKEDITOR.editor 1261 * @member CKEDITOR.editor
1193 * @param {CKEDITOR.dom.element/CKEDITOR.editable} elementOrEditable The 1262 * @param {CKEDITOR.dom.element/CKEDITOR.editable} [elementOrEditable] The
1194 * DOM element to become the editable or a {@link CKEDITOR.editable} object. 1263 * DOM element to become the editable or a {@link CKEDITOR.editable} object.
1264 * @returns {CKEDITOR.dom.element/null} The editor's editable element, or `null` if not available.
1195 */ 1265 */
1196 CKEDITOR.editor.prototype.editable = function( element ) { 1266 CKEDITOR.editor.prototype.editable = function( element ) {
1197 var editable = this._.editable; 1267 var editable = this._.editable;
@@ -1214,7 +1284,7 @@
1214 CKEDITOR.on( 'instanceLoaded', function( evt ) { 1284 CKEDITOR.on( 'instanceLoaded', function( evt ) {
1215 var editor = evt.editor; 1285 var editor = evt.editor;
1216 1286
1217 // and flag that the element was locked by our code so it'll be editable by the editor functions (#6046). 1287 // and flag that the element was locked by our code so it'll be editable by the editor functions (http://dev.ckeditor.com/ticket/6046).
1218 editor.on( 'insertElement', function( evt ) { 1288 editor.on( 'insertElement', function( evt ) {
1219 var element = evt.data; 1289 var element = evt.data;
1220 if ( element.type == CKEDITOR.NODE_ELEMENT && ( element.is( 'input' ) || element.is( 'textarea' ) ) ) { 1290 if ( element.type == CKEDITOR.NODE_ELEMENT && ( element.is( 'input' ) || element.is( 'textarea' ) ) ) {
@@ -1229,9 +1299,9 @@
1229 if ( editor.readOnly ) 1299 if ( editor.readOnly )
1230 return; 1300 return;
1231 1301
1232 // Auto fixing on some document structure weakness to enhance usabilities. (#3190 and #3189) 1302 // Auto fixing on some document structure weakness to enhance usabilities. (http://dev.ckeditor.com/ticket/3190 and http://dev.ckeditor.com/ticket/3189)
1233 var sel = editor.getSelection(); 1303 var sel = editor.getSelection();
1234 // Do it only when selection is not locked. (#8222) 1304 // Do it only when selection is not locked. (http://dev.ckeditor.com/ticket/8222)
1235 if ( sel && !sel.isLocked ) { 1305 if ( sel && !sel.isLocked ) {
1236 var isDirty = editor.checkDirty(); 1306 var isDirty = editor.checkDirty();
1237 1307
@@ -1281,7 +1351,7 @@
1281 } ); 1351 } );
1282 } ); 1352 } );
1283 1353
1284 // #9222: Show text cursor in Gecko. 1354 // http://dev.ckeditor.com/ticket/9222: Show text cursor in Gecko.
1285 // Show default cursor over control elements on all non-IEs. 1355 // Show default cursor over control elements on all non-IEs.
1286 CKEDITOR.addCss( '.cke_editable{cursor:text}.cke_editable img,.cke_editable input,.cke_editable textarea{cursor:default}' ); 1356 CKEDITOR.addCss( '.cke_editable{cursor:text}.cke_editable img,.cke_editable input,.cke_editable textarea{cursor:default}' );
1287 1357
@@ -1291,15 +1361,15 @@
1291 // 1361 //
1292 // 1362 //
1293 1363
1294 var isNotWhitespace = CKEDITOR.dom.walker.whitespaces( true ), 1364 isNotWhitespace = CKEDITOR.dom.walker.whitespaces( true ),
1295 isNotBookmark = CKEDITOR.dom.walker.bookmark( false, true ), 1365 isNotBookmark = CKEDITOR.dom.walker.bookmark( false, true ),
1296 isEmpty = CKEDITOR.dom.walker.empty(), 1366 isEmpty = CKEDITOR.dom.walker.empty(),
1297 isBogus = CKEDITOR.dom.walker.bogus(), 1367 isBogus = CKEDITOR.dom.walker.bogus(),
1298 // Matching an empty paragraph at the end of document. 1368 // Matching an empty paragraph at the end of document.
1299 emptyParagraphRegexp = /(^|<body\b[^>]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:<br[^>]*>|&nbsp;|\u00A0|&#160;)?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi; 1369 emptyParagraphRegexp = /(^|<body\b[^>]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:<br[^>]*>|&nbsp;|\u00A0|&#160;)?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi;
1300 1370
1301 // Auto-fixing block-less content by wrapping paragraph (#3190), prevent 1371 // Auto-fixing block-less content by wrapping paragraph (http://dev.ckeditor.com/ticket/3190), prevent
1302 // non-exitable-block by padding extra br.(#3189) 1372 // non-exitable-block by padding extra br.(http://dev.ckeditor.com/ticket/3189)
1303 // Returns truly value when dom was changed, falsy otherwise. 1373 // Returns truly value when dom was changed, falsy otherwise.
1304 function fixDom( evt ) { 1374 function fixDom( evt ) {
1305 var editor = evt.editor, 1375 var editor = evt.editor,
@@ -1320,7 +1390,7 @@
1320 } 1390 }
1321 1391
1322 // When we're in block enter mode, a new paragraph will be established 1392 // When we're in block enter mode, a new paragraph will be established
1323 // to encapsulate inline contents inside editable. (#3657) 1393 // to encapsulate inline contents inside editable. (http://dev.ckeditor.com/ticket/3657)
1324 // Don't autoparagraph if browser (namely - IE) incorrectly anchored selection 1394 // Don't autoparagraph if browser (namely - IE) incorrectly anchored selection
1325 // inside non-editable content. This happens e.g. if non-editable block is the only 1395 // inside non-editable content. This happens e.g. if non-editable block is the only
1326 // content of editable. 1396 // content of editable.
@@ -1348,7 +1418,7 @@
1348 1418
1349 selectionUpdateNeeded = 1; 1419 selectionUpdateNeeded = 1;
1350 1420
1351 // Cancel this selection change in favor of the next (correct). (#6811) 1421 // Cancel this selection change in favor of the next (correct). (http://dev.ckeditor.com/ticket/6811)
1352 evt.cancel(); 1422 evt.cancel();
1353 } 1423 }
1354 } 1424 }
@@ -1364,13 +1434,13 @@
1364 if ( selection.isFake ) 1434 if ( selection.isFake )
1365 return 0; 1435 return 0;
1366 1436
1367 // Ensure bogus br could help to move cursor (out of styles) to the end of block. (#7041) 1437 // Ensure bogus br could help to move cursor (out of styles) to the end of block. (http://dev.ckeditor.com/ticket/7041)
1368 var pathBlock = path.block || path.blockLimit, 1438 var pathBlock = path.block || path.blockLimit,
1369 lastNode = pathBlock && pathBlock.getLast( isNotEmpty ); 1439 lastNode = pathBlock && pathBlock.getLast( isNotEmpty );
1370 1440
1371 // Check some specialities of the current path block: 1441 // Check some specialities of the current path block:
1372 // 1. It is really displayed as block; (#7221) 1442 // 1. It is really displayed as block; (http://dev.ckeditor.com/ticket/7221)
1373 // 2. It doesn't end with one inner block; (#7467) 1443 // 2. It doesn't end with one inner block; (http://dev.ckeditor.com/ticket/7467)
1374 // 3. It doesn't have bogus br yet. 1444 // 3. It doesn't have bogus br yet.
1375 if ( 1445 if (
1376 pathBlock && pathBlock.isBlockBoundary() && 1446 pathBlock && pathBlock.isBlockBoundary() &&
@@ -1507,7 +1577,7 @@
1507 // Whether in given context (pathBlock, pathBlockLimit and editor settings) 1577 // Whether in given context (pathBlock, pathBlockLimit and editor settings)
1508 // editor should automatically wrap inline contents with blocks. 1578 // editor should automatically wrap inline contents with blocks.
1509 function shouldAutoParagraph( editor, pathBlock, pathBlockLimit ) { 1579 function shouldAutoParagraph( editor, pathBlock, pathBlockLimit ) {
1510 // Check whether pathBlock equals pathBlockLimit to support nested editable (#12162). 1580 // Check whether pathBlock equals pathBlockLimit to support nested editable (http://dev.ckeditor.com/ticket/12162).
1511 return editor.config.autoParagraph !== false && 1581 return editor.config.autoParagraph !== false &&
1512 editor.activeEnterMode != CKEDITOR.ENTER_BR && 1582 editor.activeEnterMode != CKEDITOR.ENTER_BR &&
1513 ( 1583 (
@@ -1523,7 +1593,7 @@
1523 // 1593 //
1524 // Functions related to insertXXX methods 1594 // Functions related to insertXXX methods
1525 // 1595 //
1526 var insert = ( function() { 1596 insert = ( function() {
1527 'use strict'; 1597 'use strict';
1528 1598
1529 var DTD = CKEDITOR.dtd; 1599 var DTD = CKEDITOR.dtd;
@@ -1574,7 +1644,7 @@
1574 1644
1575 // Select range and stop execution. 1645 // Select range and stop execution.
1576 // If data has been totally emptied after the filtering, 1646 // If data has been totally emptied after the filtering,
1577 // any insertion is pointless (#10339). 1647 // any insertion is pointless (http://dev.ckeditor.com/ticket/10339).
1578 if ( data && processDataForInsertion( that, data ) ) { 1648 if ( data && processDataForInsertion( that, data ) ) {
1579 // DATA INSERTION 1649 // DATA INSERTION
1580 insertDataIntoRange( that ); 1650 insertDataIntoRange( that );
@@ -1959,7 +2029,7 @@
1959 nodeName = node.getName(); 2029 nodeName = node.getName();
1960 2030
1961 // Extract only the list items, when insertion happens 2031 // Extract only the list items, when insertion happens
1962 // inside of a list, reads as rearrange list items. (#7957) 2032 // inside of a list, reads as rearrange list items. (http://dev.ckeditor.com/ticket/7957)
1963 if ( insideOfList && nodeName in CKEDITOR.dtd.$list ) { 2033 if ( insideOfList && nodeName in CKEDITOR.dtd.$list ) {
1964 nodesData = nodesData.concat( extractNodesData( node, that ) ); 2034 nodesData = nodesData.concat( extractNodesData( node, that ) );
1965 continue; 2035 continue;
@@ -2207,7 +2277,7 @@
2207 } 2277 }
2208 2278
2209 // Don't use String.replace because it fails in IE7 if special replacement 2279 // Don't use String.replace because it fails in IE7 if special replacement
2210 // characters ($$, $&, etc.) are in data (#10367). 2280 // characters ($$, $&, etc.) are in data (http://dev.ckeditor.com/ticket/10367).
2211 return wrapper.getOuterHtml().split( '{cke-peak}' ).join( data ); 2281 return wrapper.getOuterHtml().split( '{cke-peak}' ).join( data );
2212 } 2282 }
2213 2283
@@ -2232,7 +2302,7 @@
2232 // 1. Fixes a range which is a result of deleteContents() and is placed in an intermediate element (see dtd.$intermediate), 2302 // 1. Fixes a range which is a result of deleteContents() and is placed in an intermediate element (see dtd.$intermediate),
2233 // inside a table. A goal is to find a closest <td> or <th> element and when this fails, recreate the structure of the table. 2303 // inside a table. A goal is to find a closest <td> or <th> element and when this fails, recreate the structure of the table.
2234 // 2. Fixes empty cells by appending bogus <br>s or deleting empty text nodes in IE<=8 case. 2304 // 2. Fixes empty cells by appending bogus <br>s or deleting empty text nodes in IE<=8 case.
2235 var fixTableAfterContentsDeletion = ( function() { 2305 fixTableAfterContentsDeletion = ( function() {
2236 // Creates an element walker which can only "go deeper". It won't 2306 // Creates an element walker which can only "go deeper". It won't
2237 // move out from any element. Therefore it can be used to find <td>x</td> in cases like: 2307 // move out from any element. Therefore it can be used to find <td>x</td> in cases like:
2238 // <table><tbody><tr><td>x</td></tr></tbody>^<tfoot>... 2308 // <table><tbody><tr><td>x</td></tr></tbody>^<tfoot>...
@@ -2330,6 +2400,65 @@
2330 }; 2400 };
2331 } )(); 2401 } )();
2332 2402
2403 fixListAfterContentsDelete = ( function() {
2404 // Creates an element walker which operates only within lists.
2405 function getFixListSelectionWalker( testRange ) {
2406 var walker = new CKEDITOR.dom.walker( testRange );
2407 walker.guard = function( node, isMovingOut ) {
2408 if ( isMovingOut )
2409 return false;
2410 if ( node.type == CKEDITOR.NODE_ELEMENT )
2411 return node.is( CKEDITOR.dtd.$list ) || node.is( CKEDITOR.dtd.$listItem );
2412 };
2413 walker.evaluator = function( node ) {
2414 return node.type == CKEDITOR.NODE_ELEMENT && node.is( CKEDITOR.dtd.$listItem );
2415 };
2416
2417 return walker;
2418 }
2419
2420 return function( range ) {
2421 var container = range.startContainer,
2422 appendToStart = false,
2423 testRange,
2424 deeperSibling;
2425
2426 // Look left.
2427 testRange = range.clone();
2428 testRange.setStart( container, 0 );
2429 deeperSibling = getFixListSelectionWalker( testRange ).lastBackward();
2430
2431 // If left is empty, look right.
2432 if ( !deeperSibling ) {
2433 testRange = range.clone();
2434 testRange.setEndAt( container, CKEDITOR.POSITION_BEFORE_END );
2435 deeperSibling = getFixListSelectionWalker( testRange ).lastForward();
2436 appendToStart = true;
2437 }
2438
2439 // If there's no deeper nested element in both direction - container is empty - we'll use it then.
2440 if ( !deeperSibling )
2441 deeperSibling = container;
2442
2443 // We found a list what means that it's empty - remove it completely.
2444 if ( deeperSibling.is( CKEDITOR.dtd.$list ) ) {
2445 range.setStartAt( deeperSibling, CKEDITOR.POSITION_BEFORE_START );
2446 range.collapse( true );
2447 deeperSibling.remove();
2448 return;
2449 }
2450
2451 // To avoid setting selection after bogus, remove it from the target list item.
2452 // We can safely do that, because we'll insert element into that cell.
2453 var bogus = deeperSibling.getBogus();
2454 if ( bogus )
2455 bogus.remove();
2456
2457 range.moveToPosition( deeperSibling, appendToStart ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_END );
2458 range.select();
2459 };
2460 } )();
2461
2333 function mergeBlocksCollapsedSelection( editor, range, backspace, startPath ) { 2462 function mergeBlocksCollapsedSelection( editor, range, backspace, startPath ) {
2334 var startBlock = startPath.block; 2463 var startBlock = startPath.block;
2335 2464
@@ -2409,7 +2538,7 @@
2409 if ( ( bogus = startBlock.getBogus() ) ) 2538 if ( ( bogus = startBlock.getBogus() ) )
2410 bogus.remove(); 2539 bogus.remove();
2411 2540
2412 // Changing end container to element from text node (#12503). 2541 // Changing end container to element from text node (http://dev.ckeditor.com/ticket/12503).
2413 range.enlarge( CKEDITOR.ENLARGE_INLINE ); 2542 range.enlarge( CKEDITOR.ENLARGE_INLINE );
2414 2543
2415 // Delete range contents. Do NOT merge. Merging is weird. 2544 // Delete range contents. Do NOT merge. Merging is weird.
@@ -2432,7 +2561,7 @@
2432 range = editor.getSelection().getRanges()[ 0 ]; 2561 range = editor.getSelection().getRanges()[ 0 ];
2433 range.collapse( 1 ); 2562 range.collapse( 1 );
2434 2563
2435 // Optimizing range containers from text nodes to elements (#12503). 2564 // Optimizing range containers from text nodes to elements (http://dev.ckeditor.com/ticket/12503).
2436 range.optimize(); 2565 range.optimize();
2437 if ( range.startContainer.getHtml() === '' ) { 2566 if ( range.startContainer.getHtml() === '' ) {
2438 range.startContainer.appendBogus(); 2567 range.startContainer.appendBogus();
@@ -2473,7 +2602,7 @@
2473 // 2602 //
2474 // Helpers for editable.getHtmlFromRange. 2603 // Helpers for editable.getHtmlFromRange.
2475 // 2604 //
2476 var getHtmlFromRangeHelpers = { 2605 getHtmlFromRangeHelpers = {
2477 eol: { 2606 eol: {
2478 detect: function( that, editable ) { 2607 detect: function( that, editable ) {
2479 var range = that.range, 2608 var range = that.range,
@@ -2638,7 +2767,7 @@
2638 // 2767 //
2639 // Helpers for editable.extractHtmlFromRange. 2768 // Helpers for editable.extractHtmlFromRange.
2640 // 2769 //
2641 var extractHtmlFromRangeHelpers = ( function() { 2770 extractHtmlFromRangeHelpers = ( function() {
2642 function optimizeBookmarkNode( node, toStart ) { 2771 function optimizeBookmarkNode( node, toStart ) {
2643 var parent = node.getParent(); 2772 var parent = node.getParent();
2644 2773
@@ -2654,7 +2783,7 @@
2654 while ( ( next = endBookmark.getNext() ) ) { 2783 while ( ( next = endBookmark.getNext() ) ) {
2655 next.insertAfter( startBookmark ); 2784 next.insertAfter( startBookmark );
2656 2785
2657 // Update startBookmark after insertion to avoid the reversal of nodes (#13449). 2786 // Update startBookmark after insertion to avoid the reversal of nodes (http://dev.ckeditor.com/ticket/13449).
2658 startBookmark = next; 2787 startBookmark = next;
2659 } 2788 }
2660 2789
@@ -2805,7 +2934,7 @@
2805 2934
2806 walker.guard = function( node, leaving ) { 2935 walker.guard = function( node, leaving ) {
2807 // Guard may be executed on some node boundaries multiple times, 2936 // Guard may be executed on some node boundaries multiple times,
2808 // what results in creating more than one range for each selected cell. (#12964) 2937 // what results in creating more than one range for each selected cell. (http://dev.ckeditor.com/ticket/12964)
2809 if ( node.type == CKEDITOR.NODE_ELEMENT ) { 2938 if ( node.type == CKEDITOR.NODE_ELEMENT ) {
2810 var key = 'visited_' + ( leaving ? 'out' : 'in' ); 2939 var key = 'visited_' + ( leaving ? 'out' : 'in' );
2811 if ( node.getCustomData( key ) ) { 2940 if ( node.getCustomData( key ) ) {
diff --git a/sources/core/editor.js b/sources/core/editor.js
index 31188d2..640cec0 100644
--- a/sources/core/editor.js
+++ b/sources/core/editor.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
@@ -29,7 +29,7 @@
29 // Call the CKEDITOR.event constructor to initialize this instance. 29 // Call the CKEDITOR.event constructor to initialize this instance.
30 CKEDITOR.event.call( this ); 30 CKEDITOR.event.call( this );
31 31
32 // Make a clone of the config object, to avoid having it touched by our code. (#9636) 32 // Make a clone of the config object, to avoid having it touched by our code. (http://dev.ckeditor.com/ticket/9636)
33 instanceConfig = instanceConfig && CKEDITOR.tools.clone( instanceConfig ); 33 instanceConfig = instanceConfig && CKEDITOR.tools.clone( instanceConfig );
34 34
35 // if editor is created off one page element. 35 // if editor is created off one page element.
@@ -223,7 +223,7 @@
223 function updateCommandsContext( editor, path, forceRefresh ) { 223 function updateCommandsContext( editor, path, forceRefresh ) {
224 // Commands cannot be refreshed without a path. In edge cases 224 // Commands cannot be refreshed without a path. In edge cases
225 // it may happen that there's no selection when this function is executed. 225 // it may happen that there's no selection when this function is executed.
226 // For example when active filter is changed in #10877. 226 // For example when active filter is changed in http://dev.ckeditor.com/ticket/10877.
227 if ( !path ) 227 if ( !path )
228 return; 228 return;
229 229
@@ -270,7 +270,7 @@
270 } else { 270 } else {
271 // Load the custom configuration file. 271 // Load the custom configuration file.
272 // To resolve customConfig race conflicts, use scriptLoader#queue 272 // To resolve customConfig race conflicts, use scriptLoader#queue
273 // instead of scriptLoader#load (#6504). 273 // instead of scriptLoader#load (http://dev.ckeditor.com/ticket/6504).
274 CKEDITOR.scriptLoader.queue( customConfig, function() { 274 CKEDITOR.scriptLoader.queue( customConfig, function() {
275 // If the CKEDITOR.editorConfig function has been properly 275 // If the CKEDITOR.editorConfig function has been properly
276 // defined in the custom configuration file, cache it. 276 // defined in the custom configuration file, cache it.
@@ -654,24 +654,49 @@
654 return editor.blockless ? CKEDITOR.ENTER_BR : enterMode; 654 return editor.blockless ? CKEDITOR.ENTER_BR : enterMode;
655 } 655 }
656 656
657 // Create DocumentFragment from specified ranges. For now it handles only tables in Firefox 657 // Create DocumentFragment from specified ranges. For now it handles only tables
658 // and returns DocumentFragment from the 1. range for other cases. (#13884) 658 // and returns DocumentFragment from the 1. range for other cases. (http://dev.ckeditor.com/ticket/13884)
659 function createDocumentFragmentFromRanges( ranges, editable ) { 659 function createDocumentFragmentFromRanges( ranges, editable ) {
660 var docFragment = new CKEDITOR.dom.documentFragment(), 660 var docFragment = new CKEDITOR.dom.documentFragment(),
661 tableClone, 661 tableClone,
662 currentRow, 662 currentRow,
663 currentRowClone; 663 currentRowClone;
664 664
665 // We must handle two cases here:
666 // 1. <tr>[<td>Cell</td>]</tr> (IE9+, Edge, Chrome, Firefox)
667 // 2. <td>[Cell]</td> (IE8-, Safari)
668 function isSelectedCell( range ) {
669 var start = range.startContainer,
670 end = range.endContainer;
671
672 if ( start.is && ( start.is( 'tr' ) ||
673 ( start.is( 'td' ) && start.equals( end ) && range.endOffset === start.getChildCount() ) ) ) {
674 return true;
675 }
676
677 return false;
678 }
679
680 function cloneCell( range ) {
681 var start = range.startContainer;
682
683 if ( start.is( 'tr' ) ) {
684 return range.cloneContents();
685 }
686
687 return start.clone( true );
688 }
689
665 for ( var i = 0; i < ranges.length; i++ ) { 690 for ( var i = 0; i < ranges.length; i++ ) {
666 var range = ranges[ i ], 691 var range = ranges[ i ],
667 container = range.startContainer; 692 container = range.startContainer.getAscendant( 'tr', true );
668 693
669 if ( container.getName && container.getName() == 'tr' ) { 694 if ( isSelectedCell( range ) ) {
670 if ( !tableClone ) { 695 if ( !tableClone ) {
671 tableClone = container.getAscendant( 'table' ).clone(); 696 tableClone = container.getAscendant( 'table' ).clone();
672 tableClone.append( container.getAscendant( 'tbody' ).clone() ); 697 tableClone.append( container.getAscendant( { thead: 1, tbody: 1, tfoot: 1 } ).clone() );
673 docFragment.append( tableClone ); 698 docFragment.append( tableClone );
674 tableClone = tableClone.findOne( 'tbody' ); 699 tableClone = tableClone.findOne( 'thead, tbody, tfoot' );
675 } 700 }
676 701
677 if ( !( currentRow && currentRow.equals( container ) ) ) { 702 if ( !( currentRow && currentRow.equals( container ) ) ) {
@@ -680,7 +705,7 @@
680 tableClone.append( currentRowClone ); 705 tableClone.append( currentRowClone );
681 } 706 }
682 707
683 currentRowClone.append( range.cloneContents() ); 708 currentRowClone.append( cloneCell( range ) );
684 } else { 709 } else {
685 // If there was something else copied with table, 710 // If there was something else copied with table,
686 // append it to DocumentFragment. 711 // append it to DocumentFragment.
@@ -717,7 +742,7 @@
717 // This guarantees that commands added before first editor#mode 742 // This guarantees that commands added before first editor#mode
718 // aren't immediately updated, but waits for editor#mode and that 743 // aren't immediately updated, but waits for editor#mode and that
719 // commands added later are immediately refreshed, even when added 744 // commands added later are immediately refreshed, even when added
720 // before instanceReady. #10103, #10249 745 // before instanceReady. http://dev.ckeditor.com/ticket/10103, http://dev.ckeditor.com/ticket/10249
721 if ( this.mode ) 746 if ( this.mode )
722 updateCommand( this, cmd ); 747 updateCommand( this, cmd );
723 748
@@ -761,7 +786,7 @@
761 } ); 786 } );
762 } 787 }
763 788
764 // Remove 'submit' events registered on form element before destroying.(#3988) 789 // Remove 'submit' events registered on form element before destroying.(http://dev.ckeditor.com/ticket/3988)
765 editor.on( 'destroy', function() { 790 editor.on( 'destroy', function() {
766 form.removeListener( 'submit', onSubmit ); 791 form.removeListener( 'submit', onSubmit );
767 } ); 792 } );
@@ -771,7 +796,7 @@
771 function onSubmit( evt ) { 796 function onSubmit( evt ) {
772 editor.updateElement(); 797 editor.updateElement();
773 798
774 // #8031 If textarea had required attribute and editor is empty fire 'required' event and if 799 // http://dev.ckeditor.com/ticket/8031 If textarea had required attribute and editor is empty fire 'required' event and if
775 // it was cancelled, prevent submitting the form. 800 // it was cancelled, prevent submitting the form.
776 if ( editor._.required && !element.getValue() && editor.fire( 'required' ) === false ) { 801 if ( editor._.required && !element.getValue() && editor.fire( 'required' ) === false ) {
777 // When user press save button event (evt) is undefined (see save plugin). 802 // When user press save button event (evt) is undefined (see save plugin).
@@ -860,10 +885,10 @@
860 * 885 *
861 * editorInstance.execCommand( 'bold' ); 886 * editorInstance.execCommand( 'bold' );
862 * 887 *
863 * @param {String} commandName The indentifier name of the command. 888 * @param {String} commandName The identifier name of the command.
864 * @param {Object} [data] The data to be passed to the command. 889 * @param {Object} [data] The data to be passed to the command. It defaults to
865 * @returns {Boolean} `true` if the command was executed 890 * an empty object starting from 4.7.0.
866 * successfully, otherwise `false`. 891 * @returns {Boolean} `true` if the command was executed successfully, `false` otherwise.
867 * @see CKEDITOR.editor#addCommand 892 * @see CKEDITOR.editor#addCommand
868 */ 893 */
869 execCommand: function( commandName, data ) { 894 execCommand: function( commandName, data ) {
@@ -871,7 +896,7 @@
871 896
872 var eventData = { 897 var eventData = {
873 name: commandName, 898 name: commandName,
874 commandData: data, 899 commandData: data || {},
875 command: command 900 command: command
876 }; 901 };
877 902
@@ -967,7 +992,7 @@
967 } 992 }
968 else { 993 else {
969 // If we don't have a proper element, set data to an empty string, 994 // If we don't have a proper element, set data to an empty string,
970 // as this method is expected to return a string. (#13385) 995 // as this method is expected to return a string. (http://dev.ckeditor.com/ticket/13385)
971 data = ''; 996 data = '';
972 } 997 }
973 } 998 }
@@ -1069,7 +1094,7 @@
1069 this.readOnly = isReadOnly; 1094 this.readOnly = isReadOnly;
1070 1095
1071 // Block or release BACKSPACE key according to current read-only 1096 // Block or release BACKSPACE key according to current read-only
1072 // state to prevent browser's history navigation (#9761). 1097 // state to prevent browser's history navigation (http://dev.ckeditor.com/ticket/9761).
1073 this.keystrokeHandler.blockedKeystrokes[ 8 ] = +isReadOnly; 1098 this.keystrokeHandler.blockedKeystrokes[ 8 ] = +isReadOnly;
1074 1099
1075 this.editable().setReadOnly( isReadOnly ); 1100 this.editable().setReadOnly( isReadOnly );
@@ -1198,17 +1223,20 @@
1198 */ 1223 */
1199 extractSelectedHtml: function( toString, removeEmptyBlock ) { 1224 extractSelectedHtml: function( toString, removeEmptyBlock ) {
1200 var editable = this.editable(), 1225 var editable = this.editable(),
1201 ranges = this.getSelection().getRanges(); 1226 ranges = this.getSelection().getRanges(),
1227 docFragment = new CKEDITOR.dom.documentFragment(),
1228 i;
1202 1229
1203 if ( !editable || ranges.length === 0 ) { 1230 if ( !editable || ranges.length === 0 ) {
1204 return null; 1231 return null;
1205 } 1232 }
1206 1233
1207 var range = ranges[ 0 ], 1234 for ( i = 0; i < ranges.length; i++ ) {
1208 docFragment = editable.extractHtmlFromRange( range, removeEmptyBlock ); 1235 docFragment.append( editable.extractHtmlFromRange( ranges[ i ], removeEmptyBlock ) );
1236 }
1209 1237
1210 if ( !removeEmptyBlock ) { 1238 if ( !removeEmptyBlock ) {
1211 this.getSelection().selectRanges( [ range ] ); 1239 this.getSelection().selectRanges( [ ranges[ 0 ] ] );
1212 } 1240 }
1213 1241
1214 return toString ? docFragment.getHtml() : docFragment; 1242 return toString ? docFragment.getHtml() : docFragment;
@@ -1324,6 +1352,38 @@
1324 }, 1352 },
1325 1353
1326 /** 1354 /**
1355 * Returns the keystroke that is assigned to a specified {@link CKEDITOR.command}. If no keystroke is assigned,
1356 * it returns `null`.
1357 *
1358 * Since version 4.7.0 this function also accepts a `command` parameter as a string.
1359 *
1360 * @since 4.6.0
1361 * @param {CKEDITOR.command/String} command The {@link CKEDITOR.command} instance or a string with the command name.
1362 * @returns {Number/null} The keystroke assigned to the provided command or `null` if there is no keystroke.
1363 */
1364 getCommandKeystroke: function( command ) {
1365 var commandInstance = ( typeof command === 'string' ? this.getCommand( command ) : command );
1366
1367 if ( commandInstance ) {
1368 var commandName = CKEDITOR.tools.object.findKey( this.commands, commandInstance ),
1369 keystrokes = this.keystrokeHandler.keystrokes,
1370 key;
1371
1372 // Some commands have a fake keystroke - for example CUT/COPY/PASTE commands are handled natively.
1373 if ( commandInstance.fakeKeystroke ) {
1374 return commandInstance.fakeKeystroke;
1375 }
1376
1377 for ( key in keystrokes ) {
1378 if ( keystrokes.hasOwnProperty( key ) && keystrokes[ key ] == commandName ) {
1379 return key;
1380 }
1381 }
1382 }
1383 return null;
1384 },
1385
1386 /**
1327 * Shorthand for {@link CKEDITOR.filter#addFeature}. 1387 * Shorthand for {@link CKEDITOR.filter#addFeature}.
1328 * 1388 *
1329 * @since 4.1 1389 * @since 4.1
@@ -1512,7 +1572,7 @@ CKEDITOR.ELEMENT_MODE_INLINE = 3;
1512 * @member CKEDITOR.config 1572 * @member CKEDITOR.config
1513 */ 1573 */
1514 1574
1515 /** 1575/**
1516 * Customizes the {@link CKEDITOR.editor#title human-readable title} of this editor. This title is displayed in 1576 * Customizes the {@link CKEDITOR.editor#title human-readable title} of this editor. This title is displayed in
1517 * tooltips and impacts various [accessibility aspects](#!/guide/dev_a11y-section-announcing-the-editor-on-the-page), 1577 * tooltips and impacts various [accessibility aspects](#!/guide/dev_a11y-section-announcing-the-editor-on-the-page),
1518 * e.g. it is commonly used by screen readers for distinguishing editor instances and for navigation. 1578 * e.g. it is commonly used by screen readers for distinguishing editor instances and for navigation.
@@ -1811,6 +1871,14 @@ CKEDITOR.ELEMENT_MODE_INLINE = 3;
1811 */ 1871 */
1812 1872
1813/** 1873/**
1874 * Event fired when the {@link #method-destroy} method is called,
1875 * but before destroying the editor.
1876 *
1877 * @event beforeDestroy
1878 * @param {CKEDITOR.editor} editor This editor instance.
1879 */
1880
1881/**
1814 * Internal event to get the current data. 1882 * Internal event to get the current data.
1815 * 1883 *
1816 * @event beforeGetData 1884 * @event beforeGetData
diff --git a/sources/core/editor_basic.js b/sources/core/editor_basic.js
index 14f3446..b7ab577 100644
--- a/sources/core/editor_basic.js
+++ b/sources/core/editor_basic.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/env.js b/sources/core/env.js
index 4410ce9..cbf089c 100644
--- a/sources/core/env.js
+++ b/sources/core/env.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
@@ -136,7 +136,7 @@ if ( !CKEDITOR.env ) {
136 var domain = document.domain, 136 var domain = document.domain,
137 hostname = window.location.hostname; 137 hostname = window.location.hostname;
138 138
139 return domain != hostname && domain != ( '[' + hostname + ']' ); // IPv6 IP support (#5434) 139 return domain != hostname && domain != ( '[' + hostname + ']' ); // IPv6 IP support (http://dev.ckeditor.com/ticket/5434)
140 }, 140 },
141 141
142 /** 142 /**
diff --git a/sources/core/event.js b/sources/core/event.js
index 0dd1f41..89444b5 100644
--- a/sources/core/event.js
+++ b/sources/core/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/eventInfo.js b/sources/core/eventInfo.js
index e1cd65a..ea62ac9 100644
--- a/sources/core/eventInfo.js
+++ b/sources/core/eventInfo.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/filter.js b/sources/core/filter.js
index e9d5a37..3e64fc5 100644
--- a/sources/core/filter.js
+++ b/sources/core/filter.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
@@ -158,7 +158,8 @@
158 }, 158 },
159 // Object: element name => array of transformations groups. 159 // Object: element name => array of transformations groups.
160 transformations: {}, 160 transformations: {},
161 cachedTests: {} 161 cachedTests: {},
162 cachedChecks: {}
162 }; 163 };
163 164
164 // Register filter instance. 165 // Register filter instance.
@@ -299,7 +300,7 @@
299 if ( el.attributes[ 'data-cke-filter' ] == 'off' ) 300 if ( el.attributes[ 'data-cke-filter' ] == 'off' )
300 return false; 301 return false;
301 302
302 // (#10260) Don't touch elements like spans with data-cke-* attribute since they're 303 // (http://dev.ckeditor.com/ticket/10260) Don't touch elements like spans with data-cke-* attribute since they're
303 // responsible e.g. for placing markers, bookmarks, odds and stuff. 304 // responsible e.g. for placing markers, bookmarks, odds and stuff.
304 // We love 'em and we don't wanna lose anything during the filtering. 305 // We love 'em and we don't wanna lose anything during the filtering.
305 // '|' is to avoid tricky joints like data-="foo" + cke-="bar". Yes, they're possible. 306 // '|' is to avoid tricky joints like data-="foo" + cke-="bar". Yes, they're possible.
@@ -346,7 +347,7 @@
346 if ( !element.parent ) 347 if ( !element.parent )
347 continue; 348 continue;
348 349
349 // Handle custom elements as inline elements (#12683). 350 // Handle custom elements as inline elements (http://dev.ckeditor.com/ticket/12683).
350 parentDtd = DTD[ element.parent.name ] || DTD.span; 351 parentDtd = DTD[ element.parent.name ] || DTD.span;
351 352
352 switch ( check.check ) { 353 switch ( check.check ) {
@@ -806,6 +807,32 @@
806 } )(), 807 } )(),
807 808
808 /** 809 /**
810 * Returns a clone of this filter instance.
811 *
812 * @since 4.7.3
813 * @returns {CKEDITOR.filter}
814 */
815 clone: function() {
816 var ret = new CKEDITOR.filter(),
817 clone = CKEDITOR.tools.clone;
818
819 // Cloning allowed content related things.
820 ret.allowedContent = clone( this.allowedContent );
821 ret._.allowedRules = clone( this._.allowedRules );
822
823 // Disallowed content rules.
824 ret.disallowedContent = clone( this.disallowedContent );
825 ret._.disallowedRules = clone( this._.disallowedRules );
826
827 ret._.transformations = clone( this._.transformations );
828
829 ret.disabled = this.disabled;
830 ret.editor = this.editor;
831
832 return ret;
833 },
834
835 /**
809 * Destroys the filter instance and removes it from the global {@link CKEDITOR.filter#instances} object. 836 * Destroys the filter instance and removes it from the global {@link CKEDITOR.filter#instances} object.
810 * 837 *
811 * @since 4.4.5 838 * @since 4.4.5
@@ -1248,22 +1275,20 @@
1248 styles = styleDef.styles, 1275 styles = styleDef.styles,
1249 attrs = styleDef.attributes || {}; 1276 attrs = styleDef.attributes || {};
1250 1277
1251 if ( styles ) { 1278 if ( styles && !CKEDITOR.tools.isEmpty( styles ) ) {
1252 styles = copy( styles ); 1279 styles = copy( styles );
1253 attrs.style = CKEDITOR.tools.writeCssText( styles, true ); 1280 attrs.style = CKEDITOR.tools.writeCssText( styles, true );
1254 } else { 1281 } else {
1255 styles = {}; 1282 styles = {};
1256 } 1283 }
1257 1284
1258 var el = { 1285 return {
1259 name: styleDef.element, 1286 name: styleDef.element,
1260 attributes: attrs, 1287 attributes: attrs,
1261 classes: attrs[ 'class' ] ? attrs[ 'class' ].split( /\s+/ ) : [], 1288 classes: attrs[ 'class' ] ? attrs[ 'class' ].split( /\s+/ ) : [],
1262 styles: styles, 1289 styles: styles,
1263 children: [] 1290 children: []
1264 }; 1291 };
1265
1266 return el;
1267 } 1292 }
1268 1293
1269 // Mock hash based on string. 1294 // Mock hash based on string.
@@ -1873,6 +1898,7 @@
1873 // 1898 //
1874 // TRANSFORMATIONS -------------------------------------------------------- 1899 // TRANSFORMATIONS --------------------------------------------------------
1875 // 1900 //
1901 var transformationsTools;
1876 1902
1877 // Apply given transformations group to the element. 1903 // Apply given transformations group to the element.
1878 function applyTransformationsGroup( filter, element, group ) { 1904 function applyTransformationsGroup( filter, element, group ) {
@@ -2021,7 +2047,7 @@
2021 * @class CKEDITOR.filter.transformationsTools 2047 * @class CKEDITOR.filter.transformationsTools
2022 * @singleton 2048 * @singleton
2023 */ 2049 */
2024 var transformationsTools = CKEDITOR.filter.transformationsTools = { 2050 transformationsTools = CKEDITOR.filter.transformationsTools = {
2025 /** 2051 /**
2026 * Converts `width` and `height` attributes to styles. 2052 * Converts `width` and `height` attributes to styles.
2027 * 2053 *
@@ -2070,8 +2096,8 @@
2070 * Converts length in the `styleName` style to a valid length attribute (like `width` or `height`). 2096 * Converts length in the `styleName` style to a valid length attribute (like `width` or `height`).
2071 * 2097 *
2072 * @param {CKEDITOR.htmlParser.element} element 2098 * @param {CKEDITOR.htmlParser.element} element
2073 * @param {String} styleName Name of the style that will be converted. 2099 * @param {String} styleName The name of the style that will be converted.
2074 * @param {String} [attrName=styleName] Name of the attribute into which the style will be converted. 2100 * @param {String} [attrName=styleName] The name of the attribute into which the style will be converted.
2075 */ 2101 */
2076 lengthToAttribute: function( element, styleName, attrName ) { 2102 lengthToAttribute: function( element, styleName, attrName ) {
2077 attrName = attrName || styleName; 2103 attrName = attrName || styleName;
@@ -2091,7 +2117,7 @@
2091 }, 2117 },
2092 2118
2093 /** 2119 /**
2094 * Converts the `align` attribute to the `float` style if not set. Attribute 2120 * Converts the `align` attribute to the `float` style if not set. The attribute
2095 * is always removed. 2121 * is always removed.
2096 * 2122 *
2097 * @param {CKEDITOR.htmlParser.element} element 2123 * @param {CKEDITOR.htmlParser.element} element
@@ -2109,7 +2135,7 @@
2109 2135
2110 /** 2136 /**
2111 * Converts the `float` style to the `align` attribute if not set. 2137 * Converts the `float` style to the `align` attribute if not set.
2112 * Style is always removed. 2138 * The style is always removed.
2113 * 2139 *
2114 * @param {CKEDITOR.htmlParser.element} element 2140 * @param {CKEDITOR.htmlParser.element} element
2115 */ 2141 */
@@ -2125,6 +2151,107 @@
2125 }, 2151 },
2126 2152
2127 /** 2153 /**
2154 * Converts the shorthand form of the `border` style to seperate styles.
2155 *
2156 * @param {CKEDITOR.htmlParser.element} element
2157 */
2158 splitBorderShorthand: function( element ) {
2159 if ( !element.styles.border ) {
2160 return;
2161 }
2162
2163 var widths = element.styles.border.match( /([\.\d]+\w+)/g ) || [ '0px' ];
2164 switch ( widths.length ) {
2165 case 1:
2166 element.styles[ 'border-width' ] = widths[0];
2167 break;
2168 case 2:
2169 mapStyles( [ 0, 1, 0, 1 ] );
2170 break;
2171 case 3:
2172 mapStyles( [ 0, 1, 2, 1 ] );
2173 break;
2174 case 4:
2175 mapStyles( [ 0, 1, 2, 3 ] );
2176 break;
2177 }
2178
2179 element.styles[ 'border-style' ] = element.styles[ 'border-style' ] ||
2180 ( element.styles.border.match( /(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset|initial|inherit)/ ) || [] )[ 0 ];
2181 if ( !element.styles[ 'border-style' ] )
2182 delete element.styles[ 'border-style' ];
2183
2184 delete element.styles.border;
2185
2186 function mapStyles( map ) {
2187 element.styles['border-top-width'] = widths[ map[0] ];
2188 element.styles['border-right-width'] = widths[ map[1] ];
2189 element.styles['border-bottom-width'] = widths[ map[2] ];
2190 element.styles['border-left-width'] = widths[ map[3] ];
2191 }
2192 },
2193
2194 listTypeToStyle: function( element ) {
2195 if ( element.attributes.type ) {
2196 switch ( element.attributes.type ) {
2197 case 'a':
2198 element.styles[ 'list-style-type' ] = 'lower-alpha';
2199 break;
2200 case 'A':
2201 element.styles[ 'list-style-type' ] = 'upper-alpha';
2202 break;
2203 case 'i':
2204 element.styles[ 'list-style-type' ] = 'lower-roman';
2205 break;
2206 case 'I':
2207 element.styles[ 'list-style-type' ] = 'upper-roman';
2208 break;
2209 case '1':
2210 element.styles[ 'list-style-type' ] = 'decimal';
2211 break;
2212 default:
2213 element.styles[ 'list-style-type' ] = element.attributes.type;
2214 }
2215 }
2216 },
2217
2218 /**
2219 * Converts the shorthand form of the `margin` style to seperate styles.
2220 *
2221 * @param {CKEDITOR.htmlParser.element} element
2222 */
2223 splitMarginShorthand: function( element ) {
2224 if ( !element.styles.margin ) {
2225 return;
2226 }
2227
2228 var widths = element.styles.margin.match( /(\-?[\.\d]+\w+)/g ) || [ '0px' ];
2229 switch ( widths.length ) {
2230 case 1:
2231 mapStyles( [ 0, 0, 0, 0 ] );
2232 break;
2233 case 2:
2234 mapStyles( [ 0, 1, 0, 1 ] );
2235 break;
2236 case 3:
2237 mapStyles( [ 0, 1, 2, 1 ] );
2238 break;
2239 case 4:
2240 mapStyles( [ 0, 1, 2, 3 ] );
2241 break;
2242 }
2243
2244 delete element.styles.margin;
2245
2246 function mapStyles( map ) {
2247 element.styles['margin-top'] = widths[ map[0] ];
2248 element.styles['margin-right'] = widths[ map[1] ];
2249 element.styles['margin-bottom'] = widths[ map[2] ];
2250 element.styles['margin-left'] = widths[ map[3] ];
2251 }
2252 },
2253
2254 /**
2128 * Checks whether an element matches a given {@link CKEDITOR.style}. 2255 * Checks whether an element matches a given {@link CKEDITOR.style}.
2129 * The element can be a "superset" of a style, e.g. it may have 2256 * The element can be a "superset" of a style, e.g. it may have
2130 * more classes, but needs to have at least those defined in the style. 2257 * more classes, but needs to have at least those defined in the style.
@@ -2135,12 +2262,12 @@
2135 matchesStyle: elementMatchesStyle, 2262 matchesStyle: elementMatchesStyle,
2136 2263
2137 /** 2264 /**
2138 * Transforms element to given form. 2265 * Transforms an element to a given form.
2139 * 2266 *
2140 * Form may be a: 2267 * Form may be a:
2141 * 2268 *
2142 * * {@link CKEDITOR.style}, 2269 * * {@link CKEDITOR.style},
2143 * * string &ndash; the new name of an element. 2270 * * string &ndash; the new name of the element.
2144 * 2271 *
2145 * @param {CKEDITOR.htmlParser.element} el 2272 * @param {CKEDITOR.htmlParser.element} el
2146 * @param {CKEDITOR.style/String} form 2273 * @param {CKEDITOR.style/String} form
@@ -2191,7 +2318,7 @@
2191 * * {@link CKEDITOR.filter.allowedContentRules} &ndash; defined rules will be added 2318 * * {@link CKEDITOR.filter.allowedContentRules} &ndash; defined rules will be added
2192 * to the {@link CKEDITOR.editor#filter}. 2319 * to the {@link CKEDITOR.editor#filter}.
2193 * * `true` &ndash; will disable the filter (data will not be filtered, 2320 * * `true` &ndash; will disable the filter (data will not be filtered,
2194 * all features will be activated). 2321 * all features will be activated). Reading [security best practices](#!/guide/dev_best_practices) before setting `true` is recommended.
2195 * * default &ndash; the filter will be configured by loaded features 2322 * * default &ndash; the filter will be configured by loaded features
2196 * (toolbar items, commands, etc.). 2323 * (toolbar items, commands, etc.).
2197 * 2324 *
diff --git a/sources/core/focusmanager.js b/sources/core/focusmanager.js
index ee1bc39..45c3137 100644
--- a/sources/core/focusmanager.js
+++ b/sources/core/focusmanager.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
@@ -147,8 +147,9 @@
147 * @member CKEDITOR.focusManager 147 * @member CKEDITOR.focusManager
148 */ 148 */
149 blur: function( noDelay ) { 149 blur: function( noDelay ) {
150 if ( this._.locked ) 150 if ( this._.locked ) {
151 return; 151 return;
152 }
152 153
153 function doBlur() { 154 function doBlur() {
154 if ( this.hasFocus ) { 155 if ( this.hasFocus ) {
@@ -160,13 +161,14 @@
160 } 161 }
161 } 162 }
162 163
163 if ( this._.timer ) 164 if ( this._.timer ) {
164 clearTimeout( this._.timer ); 165 clearTimeout( this._.timer );
166 }
165 167
166 var delay = CKEDITOR.focusManager._.blurDelay; 168 var delay = CKEDITOR.focusManager._.blurDelay;
167 if ( noDelay || !delay ) 169 if ( noDelay || !delay ) {
168 doBlur.call( this ); 170 doBlur.call( this );
169 else { 171 } else {
170 this._.timer = CKEDITOR.tools.setTimeout( function() { 172 this._.timer = CKEDITOR.tools.setTimeout( function() {
171 delete this._.timer; 173 delete this._.timer;
172 doBlur.call( this ); 174 doBlur.call( this );
diff --git a/sources/core/htmldataprocessor.js b/sources/core/htmldataprocessor.js
index d079e4d..79e996b 100644
--- a/sources/core/htmldataprocessor.js
+++ b/sources/core/htmldataprocessor.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
@@ -56,7 +56,7 @@
56 // it up and apply the filter. 56 // it up and apply the filter.
57 data = protectSource( data, editor ); 57 data = protectSource( data, editor );
58 58
59 // Protect content of textareas. (#9995) 59 // Protect content of textareas. (http://dev.ckeditor.com/ticket/9995)
60 // Do this before protecting attributes to avoid breaking: 60 // Do this before protecting attributes to avoid breaking:
61 // <textarea><img src="..." /></textarea> 61 // <textarea><img src="..." /></textarea>
62 data = protectElements( data, protectTextareaRegex ); 62 data = protectElements( data, protectTextareaRegex );
@@ -67,23 +67,23 @@
67 data = protectAttributes( data ); 67 data = protectAttributes( data );
68 68
69 // Protect elements than can't be set inside a DIV. E.g. IE removes 69 // Protect elements than can't be set inside a DIV. E.g. IE removes
70 // style tags from innerHTML. (#3710) 70 // style tags from innerHTML. (http://dev.ckeditor.com/ticket/3710)
71 data = protectElements( data, protectElementsRegex ); 71 data = protectElements( data, protectElementsRegex );
72 72
73 // Certain elements has problem to go through DOM operation, protect 73 // Certain elements has problem to go through DOM operation, protect
74 // them by prefixing 'cke' namespace. (#3591) 74 // them by prefixing 'cke' namespace. (http://dev.ckeditor.com/ticket/3591)
75 data = protectElementsNames( data ); 75 data = protectElementsNames( data );
76 76
77 // All none-IE browsers ignore self-closed custom elements, 77 // All none-IE browsers ignore self-closed custom elements,
78 // protecting them into open-close. (#3591) 78 // protecting them into open-close. (http://dev.ckeditor.com/ticket/3591)
79 data = protectSelfClosingElements( data ); 79 data = protectSelfClosingElements( data );
80 80
81 // Compensate one leading line break after <pre> open as browsers 81 // Compensate one leading line break after <pre> open as browsers
82 // eat it up. (#5789) 82 // eat it up. (http://dev.ckeditor.com/ticket/5789)
83 data = protectPreFormatted( data ); 83 data = protectPreFormatted( data );
84 84
85 // There are attributes which may execute JavaScript code inside fixBin. 85 // There are attributes which may execute JavaScript code inside fixBin.
86 // Encode them greedily. They will be unprotected right after getting HTML from fixBin. (#10) 86 // Encode them greedily. They will be unprotected right after getting HTML from fixBin. (http://dev.ckeditor.com/ticket/10)
87 data = protectInsecureAttributes( data ); 87 data = protectInsecureAttributes( data );
88 88
89 var fixBin = evtData.context || editor.editable().getName(), 89 var fixBin = evtData.context || editor.editable().getName(),
@@ -99,7 +99,7 @@
99 // Call the browser to help us fixing a possibly invalid HTML 99 // Call the browser to help us fixing a possibly invalid HTML
100 // structure. 100 // structure.
101 var el = editor.document.createElement( fixBin ); 101 var el = editor.document.createElement( fixBin );
102 // Add fake character to workaround IE comments bug. (#3801) 102 // Add fake character to workaround IE comments bug. (http://dev.ckeditor.com/ticket/3801)
103 el.setHtml( 'a' + data ); 103 el.setHtml( 'a' + data );
104 data = el.getHtml().substr( 1 ); 104 data = el.getHtml().substr( 1 );
105 105
@@ -128,7 +128,7 @@
128 data = CKEDITOR.htmlParser.fragment.fromHtml( data, evtData.context, fixBodyTag ); 128 data = CKEDITOR.htmlParser.fragment.fromHtml( data, evtData.context, fixBodyTag );
129 129
130 // The empty root element needs to be fixed by adding 'p' or 'div' into it. 130 // The empty root element needs to be fixed by adding 'p' or 'div' into it.
131 // This avoids the need to create that element on the first focus (#12630). 131 // This avoids the need to create that element on the first focus (http://dev.ckeditor.com/ticket/12630).
132 if ( fixBodyTag ) { 132 if ( fixBodyTag ) {
133 fixEmptyRoot( data, fixBodyTag ); 133 fixEmptyRoot( data, fixBodyTag );
134 } 134 }
@@ -163,7 +163,7 @@
163 editor.on( 'toDataFormat', function( evt ) { 163 editor.on( 'toDataFormat', function( evt ) {
164 var data = evt.data.dataValue; 164 var data = evt.data.dataValue;
165 165
166 // #10854 - we need to strip leading blockless <br> which FF adds 166 // http://dev.ckeditor.com/ticket/10854 - we need to strip leading blockless <br> which FF adds
167 // automatically when editable contains only non-editable content. 167 // automatically when editable contains only non-editable content.
168 // We do that for every browser (so it's a constant behavior) and 168 // We do that for every browser (so it's a constant behavior) and
169 // not in BR mode, in which chance of valid leading blockless <br> is higher. 169 // not in BR mode, in which chance of valid leading blockless <br> is higher.
@@ -192,7 +192,7 @@
192 data.writeChildrenHtml( writer ); 192 data.writeChildrenHtml( writer );
193 data = writer.getHtml( true ); 193 data = writer.getHtml( true );
194 194
195 // Restore those non-HTML protected source. (#4475,#4880) 195 // Restore those non-HTML protected source. (http://dev.ckeditor.com/ticket/4475,http://dev.ckeditor.com/ticket/4880)
196 data = unprotectRealComments( data ); 196 data = unprotectRealComments( data );
197 data = unprotectSource( data, editor ); 197 data = unprotectSource( data, editor );
198 198
@@ -448,7 +448,7 @@
448 return false; 448 return false;
449 449
450 // 1. For IE version >=8, empty blocks are displayed correctly themself in wysiwiyg; 450 // 1. For IE version >=8, empty blocks are displayed correctly themself in wysiwiyg;
451 // 2. For the rest, at least table cell and list item need no filler space. (#6248) 451 // 2. For the rest, at least table cell and list item need no filler space. (http://dev.ckeditor.com/ticket/6248)
452 if ( !isOutput && !CKEDITOR.env.needsBrFiller && 452 if ( !isOutput && !CKEDITOR.env.needsBrFiller &&
453 ( document.documentMode > 7 || 453 ( document.documentMode > 7 ||
454 block.name in CKEDITOR.dtd.tr || 454 block.name in CKEDITOR.dtd.tr ||
@@ -484,7 +484,7 @@
484 } 484 }
485 485
486 // Regex to scan for &nbsp; at the end of blocks, which are actually placeholders. 486 // Regex to scan for &nbsp; at the end of blocks, which are actually placeholders.
487 // Safari transforms the &nbsp; to \xa0. (#4172) 487 // Safari transforms the &nbsp; to \xa0. (http://dev.ckeditor.com/ticket/4172)
488 var tailNbspRegex = /(?:&nbsp;|\xa0)$/; 488 var tailNbspRegex = /(?:&nbsp;|\xa0)$/;
489 489
490 var protectedSourceMarker = '{cke_protected}'; 490 var protectedSourceMarker = '{cke_protected}';
@@ -563,18 +563,35 @@
563 // active in the editing area (IE|WebKit). 563 // active in the editing area (IE|WebKit).
564 [ ( /^on/ ), 'data-cke-pa-on' ], 564 [ ( /^on/ ), 'data-cke-pa-on' ],
565 565
566 // Prevent iframe's srcdoc attribute from being evaluated in the editable.
567 [ ( /^srcdoc/ ), 'data-cke-pa-srcdoc' ],
568
566 // Don't let some old expando enter editor. Concerns only IE8, 569 // Don't let some old expando enter editor. Concerns only IE8,
567 // but for consistency remove on all browsers. 570 // but for consistency remove on all browsers.
568 [ ( /^data-cke-expando$/ ), '' ] 571 [ ( /^data-cke-expando$/ ), '' ]
569 ] 572 ],
573
574 elements: {
575 // Prevent iframe's src attribute with javascript code or data protocol from being evaluated in the editable.
576 iframe: function( element ) {
577 if ( element.attributes && element.attributes.src ) {
578
579 var src = element.attributes.src.toLowerCase().replace( /[^a-z]/gi, '' );
580 if ( src.indexOf( 'javascript' ) === 0 || src.indexOf( 'data' ) === 0 ) {
581 element.attributes[ 'data-cke-pa-src' ] = element.attributes.src;
582 delete element.attributes.src;
583 }
584 }
585 }
586 }
570 }; 587 };
571 588
572 // Disable form elements editing mode provided by some browsers. (#5746) 589 // Disable form elements editing mode provided by some browsers. (http://dev.ckeditor.com/ticket/5746)
573 function protectReadOnly( element ) { 590 function protectReadOnly( element ) {
574 var attrs = element.attributes; 591 var attrs = element.attributes;
575 592
576 // We should flag that the element was locked by our code so 593 // We should flag that the element was locked by our code so
577 // it'll be editable by the editor functions (#6046). 594 // it'll be editable by the editor functions (http://dev.ckeditor.com/ticket/6046).
578 if ( attrs.contenteditable != 'false' ) 595 if ( attrs.contenteditable != 'false' )
579 attrs[ 'data-cke-editable' ] = attrs.contenteditable ? 'true' : 1; 596 attrs[ 'data-cke-editable' ] = attrs.contenteditable ? 'true' : 1;
580 597
@@ -602,7 +619,7 @@
602 } 619 }
603 }, 620 },
604 621
605 // Remove empty link but not empty anchor. (#3829, #13516) 622 // Remove empty link but not empty anchor. (http://dev.ckeditor.com/ticket/3829, http://dev.ckeditor.com/ticket/13516)
606 a: function( element ) { 623 a: function( element ) {
607 var attrs = element.attributes; 624 var attrs = element.attributes;
608 625
@@ -641,7 +658,7 @@
641 if ( attribs[ 'data-cke-temp' ] ) 658 if ( attribs[ 'data-cke-temp' ] )
642 return false; 659 return false;
643 660
644 // Remove duplicated attributes - #3789. 661 // Remove duplicated attributes - http://dev.ckeditor.com/ticket/3789.
645 var attributeNames = [ 'name', 'href', 'src' ], 662 var attributeNames = [ 'name', 'href', 'src' ],
646 savedAttributeName; 663 savedAttributeName;
647 for ( var i = 0; i < attributeNames.length; i++ ) { 664 for ( var i = 0; i < attributeNames.length; i++ ) {
@@ -653,7 +670,7 @@
653 return element; 670 return element;
654 }, 671 },
655 672
656 // The contents of table should be in correct order (#4809). 673 // The contents of table should be in correct order (http://dev.ckeditor.com/ticket/4809).
657 table: function( element ) { 674 table: function( element ) {
658 // Clone the array as it would become empty during the sort call. 675 // Clone the array as it would become empty during the sort call.
659 var children = element.children.slice( 0 ); 676 var children = element.children.slice( 0 );
@@ -712,7 +729,7 @@
712 title: function( element ) { 729 title: function( element ) {
713 var titleText = element.children[ 0 ]; 730 var titleText = element.children[ 0 ];
714 731
715 // Append text-node to title tag if not present (i.e. non-IEs) (#9882). 732 // Append text-node to title tag if not present (i.e. non-IEs) (http://dev.ckeditor.com/ticket/9882).
716 !titleText && append( element, titleText = new CKEDITOR.htmlParser.text() ); 733 !titleText && append( element, titleText = new CKEDITOR.htmlParser.text() );
717 734
718 // Transfer data-saved title to title tag. 735 // Transfer data-saved title to title tag.
@@ -733,7 +750,7 @@
733 750
734 if ( CKEDITOR.env.ie ) { 751 if ( CKEDITOR.env.ie ) {
735 // IE outputs style attribute in capital letters. We should convert 752 // IE outputs style attribute in capital letters. We should convert
736 // them back to lower case, while not hurting the values (#5930) 753 // them back to lower case, while not hurting the values (http://dev.ckeditor.com/ticket/5930)
737 defaultHtmlFilterRulesForAll.attributes.style = function( value ) { 754 defaultHtmlFilterRulesForAll.attributes.style = function( value ) {
738 return value.replace( /(^|;)([^\:]+)/g, function( match ) { 755 return value.replace( /(^|;)([^\:]+)/g, function( match ) {
739 return match.toLowerCase(); 756 return match.toLowerCase();
@@ -741,7 +758,7 @@
741 }; 758 };
742 } 759 }
743 760
744 // Disable form elements editing mode provided by some browsers. (#5746) 761 // Disable form elements editing mode provided by some browsers. (http://dev.ckeditor.com/ticket/5746)
745 function unprotectReadyOnly( element ) { 762 function unprotectReadyOnly( element ) {
746 var attrs = element.attributes; 763 var attrs = element.attributes;
747 switch ( attrs[ 'data-cke-editable' ] ) { 764 switch ( attrs[ 'data-cke-editable' ] ) {
@@ -773,7 +790,7 @@
773 // 790 //
774 // 'data-x' => '&lt;a href=&quot;X&quot;' 791 // 'data-x' => '&lt;a href=&quot;X&quot;'
775 // 792 //
776 // which, can be easily filtered out (#11508). 793 // which, can be easily filtered out (http://dev.ckeditor.com/ticket/11508).
777 protectAttributeRegex = /([\w-:]+)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi, 794 protectAttributeRegex = /([\w-:]+)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi,
778 protectAttributeNameRegex = /^(href|src|name)$/i; 795 protectAttributeNameRegex = /^(href|src|name)$/i;
779 796
@@ -790,8 +807,8 @@
790 function protectAttributes( html ) { 807 function protectAttributes( html ) {
791 return html.replace( protectElementRegex, function( element, tag, attributes ) { 808 return html.replace( protectElementRegex, function( element, tag, attributes ) {
792 return '<' + tag + attributes.replace( protectAttributeRegex, function( fullAttr, attrName ) { 809 return '<' + tag + attributes.replace( protectAttributeRegex, function( fullAttr, attrName ) {
793 // Avoid corrupting the inline event attributes (#7243). 810 // Avoid corrupting the inline event attributes (http://dev.ckeditor.com/ticket/7243).
794 // We should not rewrite the existed protected attributes, e.g. clipboard content from editor. (#5218) 811 // We should not rewrite the existed protected attributes, e.g. clipboard content from editor. (http://dev.ckeditor.com/ticket/5218)
795 if ( protectAttributeNameRegex.test( attrName ) && attributes.indexOf( 'data-cke-saved-' + attrName ) == -1 ) 812 if ( protectAttributeNameRegex.test( attrName ) && attributes.indexOf( 'data-cke-saved-' + attrName ) == -1 )
796 return ' data-cke-saved-' + fullAttr + ' data-cke-' + CKEDITOR.rnd + '-' + fullAttr; 813 return ' data-cke-saved-' + fullAttr + ' data-cke-' + CKEDITOR.rnd + '-' + fullAttr;
797 814
@@ -880,7 +897,7 @@
880 // <noscript> tags (get lost in IE and messed up in FF). 897 // <noscript> tags (get lost in IE and messed up in FF).
881 /<noscript[\s\S]*?<\/noscript>/gi, 898 /<noscript[\s\S]*?<\/noscript>/gi,
882 899
883 // Avoid meta tags being stripped (#8117). 900 // Avoid meta tags being stripped (http://dev.ckeditor.com/ticket/8117).
884 /<meta[\s\S]*?\/?>/gi 901 /<meta[\s\S]*?\/?>/gi
885 ].concat( protectRegexes ); 902 ].concat( protectRegexes );
886 903
@@ -894,7 +911,7 @@
894 911
895 for ( var i = 0; i < regexes.length; i++ ) { 912 for ( var i = 0; i < regexes.length; i++ ) {
896 data = data.replace( regexes[ i ], function( match ) { 913 data = data.replace( regexes[ i ], function( match ) {
897 match = match.replace( tempRegex, // There could be protected source inside another one. (#3869). 914 match = match.replace( tempRegex, // There could be protected source inside another one. (http://dev.ckeditor.com/ticket/3869).
898 function( $, isComment, id ) { 915 function( $, isComment, id ) {
899 return protectedHtml[ id ]; 916 return protectedHtml[ id ];
900 } ); 917 } );
@@ -912,7 +929,7 @@
912 929
913 // Different protection pattern is used for those that 930 // Different protection pattern is used for those that
914 // live in attributes to avoid from being HTML encoded. 931 // live in attributes to avoid from being HTML encoded.
915 // Why so serious? See #9205, #8216, #7805, #11754, #11846. 932 // Why so serious? See http://dev.ckeditor.com/ticket/9205, http://dev.ckeditor.com/ticket/8216, http://dev.ckeditor.com/ticket/7805, http://dev.ckeditor.com/ticket/11754, http://dev.ckeditor.com/ticket/11846.
916 data = data.replace( /<\w+(?:\s+(?:(?:[^\s=>]+\s*=\s*(?:[^'"\s>]+|'[^']*'|"[^"]*"))|[^\s=\/>]+))+\s*\/?>/g, function( match ) { 933 data = data.replace( /<\w+(?:\s+(?:(?:[^\s=>]+\s*=\s*(?:[^'"\s>]+|'[^']*'|"[^"]*"))|[^\s=\/>]+))+\s*\/?>/g, function( match ) {
917 return match.replace( /<!--\{cke_protected\}([^>]*)-->/g, function( match, data ) { 934 return match.replace( /<!--\{cke_protected\}([^>]*)-->/g, function( match, data ) {
918 store[ store.id ] = decodeURIComponent( data ); 935 store[ store.id ] = decodeURIComponent( data );
@@ -922,7 +939,7 @@
922 939
923 // This RegExp searches for innerText in all the title/iframe/textarea elements. 940 // This RegExp searches for innerText in all the title/iframe/textarea elements.
924 // This is because browser doesn't allow HTML in these elements, that's why we can't 941 // This is because browser doesn't allow HTML in these elements, that's why we can't
925 // nest comments in there. (#11223) 942 // nest comments in there. (http://dev.ckeditor.com/ticket/11223)
926 data = data.replace( /<(title|iframe|textarea)([^>]*)>([\s\S]*?)<\/\1>/g, function( match, tagName, tagAttributes, innerText ) { 943 data = data.replace( /<(title|iframe|textarea)([^>]*)>([\s\S]*?)<\/\1>/g, function( match, tagName, tagAttributes, innerText ) {
927 return '<' + tagName + tagAttributes + '>' + unprotectSource( unprotectRealComments( innerText ), editor ) + '</' + tagName + '>'; 944 return '<' + tagName + tagAttributes + '>' + unprotectSource( unprotectRealComments( innerText ), editor ) + '</' + tagName + '>';
928 } ); 945 } );
@@ -971,7 +988,7 @@
971 * {@link CKEDITOR.htmlParser.fragment} {@link CKEDITOR.htmlParser.element}. 988 * {@link CKEDITOR.htmlParser.fragment} {@link CKEDITOR.htmlParser.element}.
972 * * 5-9: Data is available in the parsed format, but {@link CKEDITOR.htmlDataProcessor#dataFilter} 989 * * 5-9: Data is available in the parsed format, but {@link CKEDITOR.htmlDataProcessor#dataFilter}
973 * is not applied yet. 990 * is not applied yet.
974 * * 6: Data is filtered with the {CKEDITOR.filter content filter}. 991 * * 6: Data is filtered with the {@link CKEDITOR.filter content filter}.
975 * * 10: Data is processed with {@link CKEDITOR.htmlDataProcessor#dataFilter}. 992 * * 10: Data is processed with {@link CKEDITOR.htmlDataProcessor#dataFilter}.
976 * * 10-14: Data is available in the parsed format and {@link CKEDITOR.htmlDataProcessor#dataFilter} 993 * * 10-14: Data is available in the parsed format and {@link CKEDITOR.htmlDataProcessor#dataFilter}
977 * has already been applied. 994 * has already been applied.
diff --git a/sources/core/htmlparser.js b/sources/core/htmlparser.js
index dffde95..c95257c 100644
--- a/sources/core/htmlparser.js
+++ b/sources/core/htmlparser.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
@@ -163,7 +163,7 @@ CKEDITOR.htmlParser = function() {
163 tagName = tagName.toLowerCase(); 163 tagName = tagName.toLowerCase();
164 164
165 // There are some tag names that can break things, so let's 165 // There are some tag names that can break things, so let's
166 // simply ignore them when parsing. (#5224) 166 // simply ignore them when parsing. (http://dev.ckeditor.com/ticket/5224)
167 if ( /="/.test( tagName ) ) 167 if ( /="/.test( tagName ) )
168 continue; 168 continue;
169 169
diff --git a/sources/core/htmlparser/basicwriter.js b/sources/core/htmlparser/basicwriter.js
index 62a97ef..61447f0 100644
--- a/sources/core/htmlparser/basicwriter.js
+++ b/sources/core/htmlparser/basicwriter.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
@@ -67,7 +67,7 @@ CKEDITOR.htmlParser.basicWriter = CKEDITOR.tools.createClass( {
67 * @param {String} attValue The attribute value. 67 * @param {String} attValue The attribute value.
68 */ 68 */
69 attribute: function( attName, attValue ) { 69 attribute: function( attName, attValue ) {
70 // Browsers don't always escape special character in attribute values. (#4683, #4719). 70 // Browsers don't always escape special character in attribute values. (http://dev.ckeditor.com/ticket/4683, http://dev.ckeditor.com/ticket/4719).
71 if ( typeof attValue == 'string' ) 71 if ( typeof attValue == 'string' )
72 attValue = CKEDITOR.tools.htmlEncodeAttr( attValue ); 72 attValue = CKEDITOR.tools.htmlEncodeAttr( attValue );
73 73
diff --git a/sources/core/htmlparser/cdata.js b/sources/core/htmlparser/cdata.js
index 4ece2b7..be8c5cf 100644
--- a/sources/core/htmlparser/cdata.js
+++ b/sources/core/htmlparser/cdata.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/htmlparser/comment.js b/sources/core/htmlparser/comment.js
index 171c62e..14a38f3 100644
--- a/sources/core/htmlparser/comment.js
+++ b/sources/core/htmlparser/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/htmlparser/element.js b/sources/core/htmlparser/element.js
index 3654322..224d3e6 100644
--- a/sources/core/htmlparser/element.js
+++ b/sources/core/htmlparser/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
@@ -35,7 +35,7 @@ CKEDITOR.htmlParser.element = function( name, attributes ) {
35 */ 35 */
36 this.children = []; 36 this.children = [];
37 37
38 // Reveal the real semantic of our internal custom tag name (#6639), 38 // Reveal the real semantic of our internal custom tag name (http://dev.ckeditor.com/ticket/6639),
39 // when resolving whether it's block like. 39 // when resolving whether it's block like.
40 var realName = name || '', 40 var realName = name || '',
41 prefixed = realName.match( /^cke:(.*)/ ); 41 prefixed = realName.match( /^cke:(.*)/ );
@@ -56,7 +56,7 @@ CKEDITOR.htmlParser.element = function( name, attributes ) {
56}; 56};
57 57
58/** 58/**
59 * Object presentation of CSS style declaration text. 59 * Object presentation of the CSS style declaration text.
60 * 60 *
61 * @class 61 * @class
62 * @constructor Creates a `cssStyle` class instance. 62 * @constructor Creates a `cssStyle` class instance.
@@ -419,7 +419,7 @@ CKEDITOR.htmlParser.cssStyle = function() {
419 * 419 *
420 * @since 4.3 420 * @since 4.3
421 * @param {Number} index Index at which the element will be split &mdash; `0` means the beginning, 421 * @param {Number} index Index at which the element will be split &mdash; `0` means the beginning,
422 * `1` after first child node, etc. 422 * `1` after the first child node, etc.
423 * @returns {CKEDITOR.htmlParser.element} The new element following this one. 423 * @returns {CKEDITOR.htmlParser.element} The new element following this one.
424 */ 424 */
425 split: function( index ) { 425 split: function( index ) {
@@ -443,6 +443,38 @@ CKEDITOR.htmlParser.cssStyle = function() {
443 }, 443 },
444 444
445 /** 445 /**
446 * Searches through the current node children to find nodes matching the `criteria`.
447 *
448 * @param {String/Function} criteria Tag name or evaluator function.
449 * @param {Boolean} [recursive=false]
450 * @returns {CKEDITOR.htmlParser.node[]}
451 */
452 find: function( criteria, recursive ) {
453 if ( recursive === undefined ) {
454 recursive = false;
455 }
456
457 var ret = [],
458 i;
459
460 for ( i = 0; i < this.children.length; i++ ) {
461 var curChild = this.children[ i ];
462
463 if ( typeof criteria == 'function' && criteria( curChild ) ) {
464 ret.push( curChild );
465 } else if ( typeof criteria == 'string' && curChild.name === criteria ) {
466 ret.push( curChild );
467 }
468
469 if ( recursive && curChild.find ) {
470 ret = ret.concat( curChild.find( criteria, recursive ) );
471 }
472 }
473
474 return ret;
475 },
476
477 /**
446 * Adds a class name to the list of classes. 478 * Adds a class name to the list of classes.
447 * 479 *
448 * @since 4.4 480 * @since 4.4
@@ -511,8 +543,8 @@ CKEDITOR.htmlParser.cssStyle = function() {
511 543
512 if ( !ctx.nonEditable && this.attributes.contenteditable == 'false' ) 544 if ( !ctx.nonEditable && this.attributes.contenteditable == 'false' )
513 changes.push( 'nonEditable', true ); 545 changes.push( 'nonEditable', true );
514 // A context to be given nestedEditable must be nonEditable first (by inheritance) (#11372, #11698). 546 // A context to be given nestedEditable must be nonEditable first (by inheritance) (http://dev.ckeditor.com/ticket/11372, http://dev.ckeditor.com/ticket/11698).
515 // Special case: #11504 - filter starts on <body contenteditable=true>, 547 // Special case: http://dev.ckeditor.com/ticket/11504 - filter starts on <body contenteditable=true>,
516 // so ctx.nonEditable has not been yet set to true. 548 // so ctx.nonEditable has not been yet set to true.
517 else if ( ctx.nonEditable && !ctx.nestedEditable && this.attributes.contenteditable == 'true' ) 549 else if ( ctx.nonEditable && !ctx.nestedEditable && this.attributes.contenteditable == 'true' )
518 changes.push( 'nestedEditable', true ); 550 changes.push( 'nestedEditable', true );
diff --git a/sources/core/htmlparser/filter.js b/sources/core/htmlparser/filter.js
index 72767b5..db6b91c 100644
--- a/sources/core/htmlparser/filter.js
+++ b/sources/core/htmlparser/filter.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/htmlparser/fragment.js b/sources/core/htmlparser/fragment.js
index c062986..7ef915c 100644
--- a/sources/core/htmlparser/fragment.js
+++ b/sources/core/htmlparser/fragment.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
@@ -61,7 +61,7 @@ CKEDITOR.htmlParser.fragment = function() {
61 if ( node.attributes[ 'data-cke-survive' ] ) 61 if ( node.attributes[ 'data-cke-survive' ] )
62 return false; 62 return false;
63 63
64 // Empty link is to be removed when empty but not anchor. (#7894) 64 // Empty link is to be removed when empty but not anchor. (http://dev.ckeditor.com/ticket/7894)
65 return node.name == 'a' && node.attributes.href || CKEDITOR.dtd.$removeEmpty[ node.name ]; 65 return node.name == 'a' && node.attributes.href || CKEDITOR.dtd.$removeEmpty[ node.name ];
66 } 66 }
67 67
@@ -130,7 +130,7 @@ CKEDITOR.htmlParser.fragment = function() {
130 i--; 130 i--;
131 } else { 131 } else {
132 // Some element of the same type cannot be nested, flat them, 132 // Some element of the same type cannot be nested, flat them,
133 // e.g. <a href="#">foo<a href="#">bar</a></a>. (#7894) 133 // e.g. <a href="#">foo<a href="#">bar</a></a>. (http://dev.ckeditor.com/ticket/7894)
134 if ( pendingName == currentNode.name ) 134 if ( pendingName == currentNode.name )
135 addElement( currentNode, currentNode.parent, 1 ), i--; 135 addElement( currentNode, currentNode.parent, 1 ), i--;
136 } 136 }
@@ -143,7 +143,7 @@ CKEDITOR.htmlParser.fragment = function() {
143 addElement( pendingBRs.shift(), currentNode ); 143 addElement( pendingBRs.shift(), currentNode );
144 } 144 }
145 145
146 // Rtrim empty spaces on block end boundary. (#3585) 146 // Rtrim empty spaces on block end boundary. (http://dev.ckeditor.com/ticket/3585)
147 function removeTailWhitespace( element ) { 147 function removeTailWhitespace( element ) {
148 if ( element._.isBlockLike && element.name != 'pre' && element.name != 'textarea' ) { 148 if ( element._.isBlockLike && element.name != 'pre' && element.name != 'textarea' ) {
149 149
@@ -275,10 +275,10 @@ CKEDITOR.htmlParser.fragment = function() {
275 // If the element cannot be child of the current element. 275 // If the element cannot be child of the current element.
276 if ( !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] ) { 276 if ( !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] ) {
277 // Current node doesn't have a close tag, time for a close 277 // Current node doesn't have a close tag, time for a close
278 // as this element isn't fit in. (#7497) 278 // as this element isn't fit in. (http://dev.ckeditor.com/ticket/7497)
279 if ( currentNode.isOptionalClose ) 279 if ( currentNode.isOptionalClose )
280 parser.onTagClose( currentName ); 280 parser.onTagClose( currentName );
281 // Fixing malformed nested lists by moving it into a previous list item. (#3828) 281 // Fixing malformed nested lists by moving it into a previous list item. (http://dev.ckeditor.com/ticket/3828)
282 else if ( tagName in listBlocks && currentName in listBlocks ) { 282 else if ( tagName in listBlocks && currentName in listBlocks ) {
283 var children = currentNode.children, 283 var children = currentNode.children,
284 lastChild = children[ children.length - 1 ]; 284 lastChild = children[ children.length - 1 ];
@@ -291,7 +291,7 @@ CKEDITOR.htmlParser.fragment = function() {
291 currentNode = lastChild; 291 currentNode = lastChild;
292 } 292 }
293 // Establish new list root for orphan list items, but NOT to create 293 // Establish new list root for orphan list items, but NOT to create
294 // new list for the following ones, fix them instead. (#6975) 294 // new list for the following ones, fix them instead. (http://dev.ckeditor.com/ticket/6975)
295 // <dl><dt>foo<dd>bar</dl> 295 // <dl><dt>foo<dd>bar</dl>
296 // <ul><li>foo<li>bar</ul> 296 // <ul><li>foo<li>bar</ul>
297 else if ( tagName in CKEDITOR.dtd.$listItem && 297 else if ( tagName in CKEDITOR.dtd.$listItem &&
@@ -409,7 +409,7 @@ CKEDITOR.htmlParser.fragment = function() {
409 var currentName = currentNode.name, 409 var currentName = currentNode.name,
410 currentDtd = currentName ? ( CKEDITOR.dtd[ currentName ] || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) ) : rootDtd; 410 currentDtd = currentName ? ( CKEDITOR.dtd[ currentName ] || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) ) : rootDtd;
411 411
412 // Fix orphan text in list/table. (#8540) (#8870) 412 // Fix orphan text in list/table. (http://dev.ckeditor.com/ticket/8540) (http://dev.ckeditor.com/ticket/8870)
413 if ( !inTextarea && !currentDtd[ '#' ] && currentName in nonBreakingBlocks ) { 413 if ( !inTextarea && !currentDtd[ '#' ] && currentName in nonBreakingBlocks ) {
414 parser.onTagOpen( structureFixes[ currentName ] || '' ); 414 parser.onTagOpen( structureFixes[ currentName ] || '' );
415 parser.onText( text ); 415 parser.onText( text );
diff --git a/sources/core/htmlparser/node.js b/sources/core/htmlparser/node.js
index 0f1b307..b38c8a8 100644
--- a/sources/core/htmlparser/node.js
+++ b/sources/core/htmlparser/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
diff --git a/sources/core/htmlparser/text.js b/sources/core/htmlparser/text.js
index 07cb865..190e85a 100644
--- a/sources/core/htmlparser/text.js
+++ b/sources/core/htmlparser/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
diff --git a/sources/core/keystrokehandler.js b/sources/core/keystrokehandler.js
index e2a6bcd..c84089b 100644
--- a/sources/core/keystrokehandler.js
+++ b/sources/core/keystrokehandler.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/lang.js b/sources/core/lang.js
index 3519923..06f3a2a 100644
--- a/sources/core/lang.js
+++ b/sources/core/lang.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
@@ -17,10 +17,10 @@
17 * alert( CKEDITOR.lang.languages.en ); // 1 17 * alert( CKEDITOR.lang.languages.en ); // 1
18 */ 18 */
19 languages: { 19 languages: {
20 af: 1, ar: 1, bg: 1, bn: 1, bs: 1, ca: 1, cs: 1, cy: 1, da: 1, de: 1, 'de-ch': 1, el: 1, 20 af: 1, ar: 1, az: 1, bg: 1, bn: 1, bs: 1, ca: 1, cs: 1, cy: 1, da: 1, de: 1, 'de-ch': 1, el: 1,
21 'en-au': 1, 'en-ca': 1, 'en-gb': 1, en: 1, eo: 1, es: 1, et: 1, eu: 1, fa: 1, fi: 1, fo: 1, 21 'en-au': 1, 'en-ca': 1, 'en-gb': 1, en: 1, eo: 1, es: 1, 'es-mx':1, et: 1, eu: 1, fa: 1, fi: 1, fo: 1,
22 'fr-ca': 1, fr: 1, gl: 1, gu: 1, he: 1, hi: 1, hr: 1, hu: 1, id: 1, is: 1, it: 1, ja: 1, ka: 1, 22 'fr-ca': 1, fr: 1, gl: 1, gu: 1, he: 1, hi: 1, hr: 1, hu: 1, id: 1, is: 1, it: 1, ja: 1, ka: 1,
23 km: 1, ko: 1, ku: 1, lt: 1, lv: 1, mk: 1, mn: 1, ms: 1, nb: 1, nl: 1, no: 1, pl: 1, 'pt-br': 1, 23 km: 1, ko: 1, ku: 1, lt: 1, lv: 1, mk: 1, mn: 1, ms: 1, nb: 1, nl: 1, no: 1, oc: 1, pl: 1, 'pt-br': 1,
24 pt: 1, ro: 1, ru: 1, si: 1, sk: 1, sl: 1, sq: 1, 'sr-latn': 1, sr: 1, sv: 1, th: 1, tr: 1, tt: 1, ug: 1, 24 pt: 1, ro: 1, ru: 1, si: 1, sk: 1, sl: 1, sq: 1, 'sr-latn': 1, sr: 1, sv: 1, th: 1, tr: 1, tt: 1, ug: 1,
25 uk: 1, vi: 1, 'zh-cn': 1, zh: 1 25 uk: 1, vi: 1, 'zh-cn': 1, zh: 1
26 }, 26 },
diff --git a/sources/core/loader.js b/sources/core/loader.js
index 5a108df..dc02511 100644
--- a/sources/core/loader.js
+++ b/sources/core/loader.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
@@ -142,7 +142,7 @@ if ( !CKEDITOR.loader ) {
142 } 142 }
143 143
144 // We must guarantee the execution order of the scripts, so we 144 // We must guarantee the execution order of the scripts, so we
145 // need to load them one by one. (#4145) 145 // need to load them one by one. (http://dev.ckeditor.com/ticket/4145)
146 // The following if/else block has been taken from the scriptloader core code. 146 // The following if/else block has been taken from the scriptloader core code.
147 if ( typeof script.onreadystatechange !== 'undefined' ) { 147 if ( typeof script.onreadystatechange !== 'undefined' ) {
148 /** @ignore */ 148 /** @ignore */
@@ -156,7 +156,7 @@ if ( !CKEDITOR.loader ) {
156 /** @ignore */ 156 /** @ignore */
157 script.onload = function() { 157 script.onload = function() {
158 // Some browsers, such as Safari, may call the onLoad function 158 // Some browsers, such as Safari, may call the onLoad function
159 // immediately. Which will break the loading sequence. (#3661) 159 // immediately. Which will break the loading sequence. (http://dev.ckeditor.com/ticket/3661)
160 setTimeout( function() { 160 setTimeout( function() {
161 onScriptLoaded( scriptName ); 161 onScriptLoaded( scriptName );
162 }, 0 ); 162 }, 0 );
diff --git a/sources/core/log.js b/sources/core/log.js
index 6981612..228789e 100644
--- a/sources/core/log.js
+++ b/sources/core/log.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/plugindefinition.js b/sources/core/plugindefinition.js
index caff957..9ff7683 100644
--- a/sources/core/plugindefinition.js
+++ b/sources/core/plugindefinition.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/plugins.js b/sources/core/plugins.js
index 8e6c952..75cc41e 100644
--- a/sources/core/plugins.js
+++ b/sources/core/plugins.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/resourcemanager.js b/sources/core/resourcemanager.js
index 7ba88a8..c33c0f8 100644
--- a/sources/core/resourcemanager.js
+++ b/sources/core/resourcemanager.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
@@ -124,13 +124,13 @@ CKEDITOR.resourceManager.prototype = {
124 * Registers one or more resources to be loaded from an external path 124 * Registers one or more resources to be loaded from an external path
125 * instead of the core base path. 125 * instead of the core base path.
126 * 126 *
127 * // Loads a plugin from '/myplugin/samples/plugin.js'. 127 * // Loads a plugin from '/myplugins/sample/plugin.js'.
128 * CKEDITOR.plugins.addExternal( 'sample', '/myplugins/sample/' ); 128 * CKEDITOR.plugins.addExternal( 'sample', '/myplugins/sample/' );
129 * 129 *
130 * // Loads a plugin from '/myplugin/samples/my_plugin.js'. 130 * // Loads a plugin from '/myplugins/sample/my_plugin.js'.
131 * CKEDITOR.plugins.addExternal( 'sample', '/myplugins/sample/', 'my_plugin.js' ); 131 * CKEDITOR.plugins.addExternal( 'sample', '/myplugins/sample/', 'my_plugin.js' );
132 * 132 *
133 * // Loads a plugin from '/myplugin/samples/my_plugin.js'. 133 * // Loads a plugin from '/myplugins/sample/my_plugin.js'.
134 * CKEDITOR.plugins.addExternal( 'sample', '/myplugins/sample/my_plugin.js', '' ); 134 * CKEDITOR.plugins.addExternal( 'sample', '/myplugins/sample/my_plugin.js', '' );
135 * 135 *
136 * @param {String} names The resource names, separated by commas. 136 * @param {String} names The resource names, separated by commas.
diff --git a/sources/core/scriptloader.js b/sources/core/scriptloader.js
index 9ad536e..356996f 100644
--- a/sources/core/scriptloader.js
+++ b/sources/core/scriptloader.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
@@ -119,8 +119,8 @@ CKEDITOR.scriptLoader = ( function() {
119 } ); 119 } );
120 120
121 if ( callback ) { 121 if ( callback ) {
122 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 11 ) { 122 // The onload or onerror event does not fire in IE8 and IE9 Quirks Mode (http://dev.ckeditor.com/ticket/14849).
123 // FIXME: For IE, we are not able to return false on error (like 404). 123 if ( CKEDITOR.env.ie && ( CKEDITOR.env.version <= 8 || CKEDITOR.env.ie9Compat ) ) {
124 script.$.onreadystatechange = function() { 124 script.$.onreadystatechange = function() {
125 if ( script.$.readyState == 'loaded' || script.$.readyState == 'complete' ) { 125 if ( script.$.readyState == 'loaded' || script.$.readyState == 'complete' ) {
126 script.$.onreadystatechange = null; 126 script.$.onreadystatechange = null;
@@ -130,13 +130,12 @@ CKEDITOR.scriptLoader = ( function() {
130 } else { 130 } else {
131 script.$.onload = function() { 131 script.$.onload = function() {
132 // Some browsers, such as Safari, may call the onLoad function 132 // Some browsers, such as Safari, may call the onLoad function
133 // immediately. Which will break the loading sequence. (#3661) 133 // immediately. Which will break the loading sequence. (http://dev.ckeditor.com/ticket/3661)
134 setTimeout( function() { 134 setTimeout( function() {
135 onLoad( url, true ); 135 onLoad( url, true );
136 }, 0 ); 136 }, 0 );
137 }; 137 };
138 138
139 // FIXME: Opera and Safari will not fire onerror.
140 script.$.onerror = function() { 139 script.$.onerror = function() {
141 onLoad( url, false ); 140 onLoad( url, false );
142 }; 141 };
diff --git a/sources/core/selection.js b/sources/core/selection.js
index 573b890..d44db3b 100644
--- a/sources/core/selection.js
+++ b/sources/core/selection.js
@@ -1,9 +1,217 @@
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( function() { 6( function() {
7 var isMSSelection = typeof window.getSelection != 'function',
8 nextRev = 1,
9 // http://dev.ckeditor.com/ticket/13816
10 fillingCharSequence = CKEDITOR.tools.repeat( '\u200b', 7 ),
11 fillingCharSequenceRegExp = new RegExp( fillingCharSequence + '( )?', 'g' ),
12 isSelectingTable;
13
14 // #### table selection : START
15 // @param {CKEDITOR.dom.range[]} ranges
16 // @param {Boolean} allowPartially Whether a collapsed selection within table is recognized to be a valid selection.
17 // This happens for WebKits on MacOS, when you right click inside the table.
18 function isTableSelection( ranges, allowPartially ) {
19 if ( ranges.length === 0 ) {
20 return false;
21 }
22
23 var node,
24 i;
25
26 function isPartiallySelected( range ) {
27 var startCell = range.startContainer.getAscendant( { td: 1, th: 1 }, true ),
28 endCell = range.endContainer.getAscendant( { td: 1, th: 1 }, true ),
29 trim = CKEDITOR.tools.trim,
30 selected;
31
32 // Check if the selection is inside one cell and we don't have any nested table contents selected.
33 if ( !startCell || !startCell.equals( endCell ) || startCell.findOne( 'td, th, tr, tbody, table' ) ) {
34 return false;
35 }
36
37 selected = range.cloneContents();
38
39 // Empty selection is still partially selected.
40 if ( !selected.getFirst() ) {
41 return true;
42 }
43
44 return trim( selected.getFirst().getText() ) !== trim( startCell.getText() );
45 }
46
47 // Edge case: partially selected text node inside one table cell or cursor inside cell.
48 if ( !allowPartially && ranges.length === 1 &&
49 ( ranges[ 0 ].collapsed || isPartiallySelected( ranges[ 0 ] ) ) ) {
50 return false;
51 }
52
53 for ( i = 0; i < ranges.length; i++ ) {
54 node = ranges[ i ]._getTableElement();
55
56 if ( !node ) {
57 return false;
58 }
59 }
60
61 return true;
62 }
63
64 // After performing fake table selection, the real selection is limited
65 // to the first selected cell. Therefore to check if the real selection
66 // matches the fake selection, we check if the table cell from fake selection's
67 // first range and real selection's range are the same.
68 // Also if the selection is collapsed, we should check if it's placed inside the table
69 // in which the fake selection is or inside nested table. Such selection occurs after right mouse click.
70 function isRealTableSelection( selection, fakeSelection ) {
71 var ranges = selection.getRanges(),
72 fakeRanges = fakeSelection.getRanges(),
73 table = ranges.length && ranges[ 0 ]._getTableElement() &&
74 ranges[ 0 ]._getTableElement().getAscendant( 'table', true ),
75 fakeTable = fakeRanges.length && fakeRanges[ 0 ]._getTableElement() &&
76 fakeRanges[ 0 ]._getTableElement().getAscendant( 'table', true ),
77 isTableRange = ranges.length === 1 && ranges[ 0 ]._getTableElement() &&
78 ranges[ 0 ]._getTableElement().is( 'table' ),
79 isFakeTableRange = fakeRanges.length === 1 && fakeRanges[ 0 ]._getTableElement() &&
80 fakeRanges[ 0 ]._getTableElement().is( 'table' );
81
82 function isValidTableSelection( table, fakeTable, ranges, fakeRanges ) {
83 var isMenuOpen = ranges.length === 1 && ranges[ 0 ].collapsed,
84 // In case of WebKit on MacOS, when checking real selection, we must allow selection to be partial.
85 // Otherwise the check will fail for table selection with opened context menu.
86 isInTable = isTableSelection( ranges, !!CKEDITOR.env.webkit ) && isTableSelection( fakeRanges );
87
88 return isSameTable( table, fakeTable ) && ( isMenuOpen || isInTable );
89 }
90
91 function isSameTable( table, fakeTable ) {
92 if ( !table || !fakeTable ) {
93 return false;
94 }
95
96 return table.equals( fakeTable ) || fakeTable.contains( table );
97 }
98
99 if ( isValidTableSelection( table, fakeTable, ranges, fakeRanges ) ) {
100 // Edge case: when editor contains only table and that table is selected using selectAll command,
101 // then the selection is not properly refreshed and it must be done manually.
102 if ( isTableRange && !isFakeTableRange ) {
103 fakeSelection.selectRanges( ranges );
104 }
105 return true;
106 }
107
108 return false;
109 }
110
111 function getSelectedCells( ranges ) {
112 var cells = [],
113 node,
114 i;
115
116 function getCellsFromElement( element ) {
117 var cells = element.find( 'td, th' ),
118 cellsArray = [],
119 i;
120
121 for ( i = 0; i < cells.count(); i++ ) {
122 cellsArray.push( cells.getItem( i ) );
123 }
124
125 return cellsArray;
126 }
127
128 for ( i = 0; i < ranges.length; i++ ) {
129 node = ranges[ i ]._getTableElement();
130
131 if ( node.is && node.is( { td: 1, th: 1 } ) ) {
132 cells.push( node );
133 } else {
134 cells = cells.concat( getCellsFromElement( node ) );
135 }
136 }
137
138 return cells;
139 }
140
141 // Cells in the same row are separated by tab and the rows are separated by new line, e.g.
142 // Cell 1.1 Cell 1.2
143 // Cell 2.1 Cell 2.2
144 function getTextFromSelectedCells( ranges ) {
145 var cells = getSelectedCells( ranges ),
146 txt = '',
147 currentRow = [],
148 lastRow,
149 i;
150
151 for ( i = 0; i < cells.length; i++ ) {
152 if ( lastRow && !lastRow.equals( cells[ i ].getAscendant( 'tr' ) ) ) {
153 txt += currentRow.join( '\t' ) + '\n';
154 lastRow = cells[ i ].getAscendant( 'tr' );
155 currentRow = [];
156 } else if ( i === 0 ) {
157 lastRow = cells[ i ].getAscendant( 'tr' );
158 }
159
160 currentRow.push( cells[ i ].getText() );
161 }
162
163 txt += currentRow.join( '\t' );
164
165 return txt;
166 }
167
168 function performFakeTableSelection( ranges ) {
169 var editor = this.root.editor,
170 realSelection = editor.getSelection( 1 ),
171 cache;
172
173 // Cleanup after previous selection - e.g. remove hidden sel container.
174 this.reset();
175
176 // Indicate that the table is being fake-selected to prevent infinite loop
177 // inside `selectRanges`.
178 isSelectingTable = true;
179
180 // Cancel selectionchange for the real selection.
181 realSelection.root.once( 'selectionchange', function( evt ) {
182 evt.cancel();
183 }, null, null, 0 );
184
185 // Move real selection to the first selected range.
186 realSelection.selectRanges( [ ranges[ 0 ] ] );
187
188 cache = this._.cache;
189
190 // Caches given ranges.
191 cache.ranges = new CKEDITOR.dom.rangeList( ranges );
192 cache.type = CKEDITOR.SELECTION_TEXT;
193 cache.selectedElement = ranges[ 0 ]._getTableElement();
194
195 // `selectedText` should contain text from all selected data ("plain text table")
196 // to be compatible with Firefox's implementation.
197 cache.selectedText = getTextFromSelectedCells( ranges );
198
199 // Properties that will not be available when isFake.
200 cache.nativeSel = null;
201
202 this.isFake = 1;
203 this.rev = nextRev++;
204
205 // Save this selection, so it can be returned by editor.getSelection().
206 editor._.fakeSelection = this;
207
208 isSelectingTable = false;
209
210 // Fire selectionchange, just like a normal selection.
211 this.root.fire( 'selectionchange' );
212 }
213 // #### table selection : END
214
7 // #### checkSelectionChange : START 215 // #### checkSelectionChange : START
8 216
9 // The selection change check basically saves the element parent tree of 217 // The selection change check basically saves the element parent tree of
@@ -16,10 +224,9 @@
16 224
17 if ( sel ) { 225 if ( sel ) {
18 realSel = this.getSelection( 1 ); 226 realSel = this.getSelection( 1 );
19 227 // If real (not locked/stored) selection was moved from hidden container
20 // If real (not locked/stored) selection was moved from hidden container, 228 // or is not a table one, then the fake-selection must be invalidated.
21 // then the fake-selection must be invalidated. 229 if ( !realSel || ( !realSel.isHidden() && !isRealTableSelection( realSel, sel ) ) ) {
22 if ( !realSel || !realSel.isHidden() ) {
23 // Remove the cache from fake-selection references in use elsewhere. 230 // Remove the cache from fake-selection references in use elsewhere.
24 sel.reset(); 231 sel.reset();
25 232
@@ -41,8 +248,10 @@
41 248
42 var currentPath = this.elementPath(); 249 var currentPath = this.elementPath();
43 if ( !currentPath.compare( this._.selectionPreviousPath ) ) { 250 if ( !currentPath.compare( this._.selectionPreviousPath ) ) {
251 // Handle case when dialog inserts new element but parent block and path (so also focus context) does not change. (http://dev.ckeditor.com/ticket/13362)
252 var sameBlockParent = this._.selectionPreviousPath && this._.selectionPreviousPath.blockLimit.equals( currentPath.blockLimit );
44 // Cache the active element, which we'll eventually lose on Webkit. 253 // Cache the active element, which we'll eventually lose on Webkit.
45 if ( CKEDITOR.env.webkit ) 254 if ( CKEDITOR.env.webkit && !sameBlockParent )
46 this._.previousActive = this.document.getActive(); 255 this._.previousActive = this.document.getActive();
47 256
48 this._.selectionPreviousPath = currentPath; 257 this._.selectionPreviousPath = currentPath;
@@ -89,7 +298,7 @@
89 // * is a visible node, 298 // * is a visible node,
90 // * is a non-empty element (this rule will accept elements like <strong></strong> because they 299 // * is a non-empty element (this rule will accept elements like <strong></strong> because they
91 // they were not accepted by the isVisible() check, not not <br> which cannot absorb the caret). 300 // they were not accepted by the isVisible() check, not not <br> which cannot absorb the caret).
92 // See #12621. 301 // See http://dev.ckeditor.com/ticket/12621.
93 function mayAbsorbCaret( node ) { 302 function mayAbsorbCaret( node ) {
94 if ( isVisible( node ) ) 303 if ( isVisible( node ) )
95 return true; 304 return true;
@@ -130,8 +339,8 @@
130 if ( ctxRequiresFix( previous ) || ctxRequiresFix( next, 1 ) ) 339 if ( ctxRequiresFix( previous ) || ctxRequiresFix( next, 1 ) )
131 return true; 340 return true;
132 341
133 // Empty block/inline element is also affected. <span>^</span>, <p>^</p> (#7222) 342 // Empty block/inline element is also affected. <span>^</span>, <p>^</p> (http://dev.ckeditor.com/ticket/7222)
134 // If you found this line confusing check #12655. 343 // If you found this line confusing check http://dev.ckeditor.com/ticket/12655.
135 if ( !( previous || next ) && !( ct.type == CKEDITOR.NODE_ELEMENT && ct.isBlockBoundary() && ct.getBogus() ) ) 344 if ( !( previous || next ) && !( ct.type == CKEDITOR.NODE_ELEMENT && ct.isBlockBoundary() && ct.getBogus() ) )
136 return true; 345 return true;
137 346
@@ -147,7 +356,7 @@
147 return fillingChar; 356 return fillingChar;
148 } 357 }
149 358
150 // Checks if a filling char has been used, eventualy removing it (#1272). 359 // Checks if a filling char has been used, eventually removing it (http://dev.ckeditor.com/ticket/1272).
151 function checkFillingCharSequenceNodeReady( editable ) { 360 function checkFillingCharSequenceNodeReady( editable ) {
152 var fillingChar = editable.getCustomData( 'cke-fillingChar' ); 361 var fillingChar = editable.getCustomData( 'cke-fillingChar' );
153 362
@@ -156,6 +365,7 @@
156 // creating it. 365 // creating it.
157 if ( fillingChar.getCustomData( 'ready' ) ) { 366 if ( fillingChar.getCustomData( 'ready' ) ) {
158 removeFillingCharSequenceNode( editable ); 367 removeFillingCharSequenceNode( editable );
368 editable.editor.fire( 'selectionCheck' );
159 } else { 369 } else {
160 fillingChar.setCustomData( 'ready', 1 ); 370 fillingChar.setCustomData( 'ready', 1 );
161 } 371 }
@@ -167,7 +377,7 @@
167 377
168 if ( fillingChar ) { 378 if ( fillingChar ) {
169 // Text selection position might get mangled by 379 // Text selection position might get mangled by
170 // subsequent dom modification, save it now for restoring. (#8617) 380 // subsequent dom modification, save it now for restoring. (http://dev.ckeditor.com/ticket/8617)
171 if ( keepSelection !== false ) { 381 if ( keepSelection !== false ) {
172 var sel = editable.getDocument().getSelection().getNative(), 382 var sel = editable.getDocument().getSelection().getNative(),
173 // Be error proof. 383 // Be error proof.
@@ -203,11 +413,11 @@
203 } 413 }
204 } 414 }
205 415
206 // #13816 416 // http://dev.ckeditor.com/ticket/13816
207 function removeFillingCharSequenceString( str, nbspAware ) { 417 function removeFillingCharSequenceString( str, nbspAware ) {
208 if ( nbspAware ) { 418 if ( nbspAware ) {
209 return str.replace( fillingCharSequenceRegExp, function( m, p ) { 419 return str.replace( fillingCharSequenceRegExp, function( m, p ) {
210 // #10291 if filling char is followed by a space replace it with NBSP. 420 // http://dev.ckeditor.com/ticket/10291 if filling char is followed by a space replace it with NBSP.
211 return p ? '\xa0' : ''; 421 return p ? '\xa0' : '';
212 } ); 422 } );
213 } else { 423 } else {
@@ -234,10 +444,11 @@
234 } 444 }
235 445
236 // Creates cke_hidden_sel container and puts real selection there. 446 // Creates cke_hidden_sel container and puts real selection there.
237 function hideSelection( editor ) { 447 function hideSelection( editor, ariaLabel ) {
238 var style = CKEDITOR.env.ie ? 'display:none' : 'position:fixed;top:0;left:-1000px', 448 var content = ariaLabel || '&nbsp;',
449 style = CKEDITOR.env.ie && CKEDITOR.env.version < 14 ? 'display:none' : 'position:fixed;top:0;left:-1000px',
239 hiddenEl = CKEDITOR.dom.element.createFromHtml( 450 hiddenEl = CKEDITOR.dom.element.createFromHtml(
240 '<div data-cke-hidden-sel="1" data-cke-temp="1" style="' + style + '">&nbsp;</div>', 451 '<div data-cke-hidden-sel="1" data-cke-temp="1" style="' + style + '">' + content + '</div>',
241 editor.document ); 452 editor.document );
242 453
243 editor.fire( 'lockSnapshot' ); 454 editor.fire( 'lockSnapshot' );
@@ -382,7 +593,7 @@
382 ( enclosedNode = range.getEnclosedNode() ) && enclosedNode.type == CKEDITOR.NODE_ELEMENT ) { 593 ( enclosedNode = range.getEnclosedNode() ) && enclosedNode.type == CKEDITOR.NODE_ELEMENT ) {
383 // So far we can't say that enclosed element is non-editable. Before checking, 594 // So far we can't say that enclosed element is non-editable. Before checking,
384 // we'll shrink range (clone). Shrinking will stop on non-editable range, or 595 // we'll shrink range (clone). Shrinking will stop on non-editable range, or
385 // innermost element (#11114). 596 // innermost element (http://dev.ckeditor.com/ticket/11114).
386 clone = range.clone(); 597 clone = range.clone();
387 clone.shrink( CKEDITOR.SHRINK_ELEMENT, true ); 598 clone.shrink( CKEDITOR.SHRINK_ELEMENT, true );
388 599
@@ -516,7 +727,7 @@
516 727
517 // Give the editable an initial selection on first focus, 728 // Give the editable an initial selection on first focus,
518 // put selection at a consistent position at the start 729 // put selection at a consistent position at the start
519 // of the contents. (#9507) 730 // of the contents. (http://dev.ckeditor.com/ticket/9507)
520 if ( CKEDITOR.env.gecko ) { 731 if ( CKEDITOR.env.gecko ) {
521 editable.attachListener( editable, 'focus', function( evt ) { 732 editable.attachListener( editable, 'focus', function( evt ) {
522 evt.removeListener(); 733 evt.removeListener();
@@ -524,7 +735,7 @@
524 if ( restoreSel !== 0 ) { 735 if ( restoreSel !== 0 ) {
525 var nativ = editor.getSelection().getNative(); 736 var nativ = editor.getSelection().getNative();
526 // Do it only if the native selection is at an unwanted 737 // Do it only if the native selection is at an unwanted
527 // place (at the very start of the editable). #10119 738 // place (at the very start of the editable). http://dev.ckeditor.com/ticket/10119
528 if ( nativ && nativ.isCollapsed && nativ.anchorNode == editable.$ ) { 739 if ( nativ && nativ.isCollapsed && nativ.anchorNode == editable.$ ) {
529 var rng = editor.createRange(); 740 var rng = editor.createRange();
530 rng.moveToElementEditStart( editable ); 741 rng.moveToElementEditStart( editable );
@@ -539,9 +750,17 @@
539 // On Webkit we use DOMFocusIn which is fired more often than focus - e.g. when moving from main editable 750 // On Webkit we use DOMFocusIn which is fired more often than focus - e.g. when moving from main editable
540 // to nested editable (or the opposite). Unlock selection all, but restore only when it was locked 751 // to nested editable (or the opposite). Unlock selection all, but restore only when it was locked
541 // for the same active element, what will e.g. mean restoring after displaying dialog. 752 // for the same active element, what will e.g. mean restoring after displaying dialog.
542 if ( restoreSel && CKEDITOR.env.webkit ) 753 if ( restoreSel && CKEDITOR.env.webkit ) {
543 restoreSel = editor._.previousActive && editor._.previousActive.equals( doc.getActive() ); 754 restoreSel = editor._.previousActive && editor._.previousActive.equals( doc.getActive() );
544 755
756 // On Webkit when editor uses divarea, native focus causes editable viewport to scroll
757 // to the top (when there is no active selection inside while focusing) so the scroll
758 // position should be restored after focusing back editable area. (http://dev.ckeditor.com/ticket/14659)
759 if ( restoreSel && editor._.previousScrollTop != null && editor._.previousScrollTop != editable.$.scrollTop ) {
760 editable.$.scrollTop = editor._.previousScrollTop;
761 }
762 }
763
545 editor.unlockSelection( restoreSel ); 764 editor.unlockSelection( restoreSel );
546 restoreSel = 0; 765 restoreSel = 0;
547 }, null, null, -1 ); 766 }, null, null, -1 );
@@ -588,7 +807,7 @@
588 editable.attachListener( editable, 'mousedown', function( evt ) { 807 editable.attachListener( editable, 'mousedown', function( evt ) {
589 // IE scrolls document to top on right mousedown 808 // IE scrolls document to top on right mousedown
590 // when editor has no focus, remember this scroll 809 // when editor has no focus, remember this scroll
591 // position and revert it before context menu opens. (#5778) 810 // position and revert it before context menu opens. (http://dev.ckeditor.com/ticket/5778)
592 if ( evt.data.$.button == 2 ) { 811 if ( evt.data.$.button == 2 ) {
593 var sel = editor.document.getSelection(); 812 var sel = editor.document.getSelection();
594 if ( !sel || sel.getType() == CKEDITOR.SELECTION_NONE ) 813 if ( !sel || sel.getType() == CKEDITOR.SELECTION_NONE )
@@ -607,9 +826,12 @@
607 826
608 // When content doc is in standards mode, IE doesn't focus the editor when 827 // When content doc is in standards mode, IE doesn't focus the editor when
609 // clicking at the region below body (on html element) content, we emulate 828 // clicking at the region below body (on html element) content, we emulate
610 // the normal behavior on old IEs. (#1659, #7932) 829 // the normal behavior on old IEs. (http://dev.ckeditor.com/ticket/1659, http://dev.ckeditor.com/ticket/7932)
611 if ( doc.$.compatMode != 'BackCompat' ) { 830 if ( doc.$.compatMode != 'BackCompat' ) {
612 if ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) { 831 if ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) {
832 var textRng,
833 startRng;
834
613 html.on( 'mousedown', function( evt ) { 835 html.on( 'mousedown', function( evt ) {
614 evt = evt.data; 836 evt = evt.data;
615 837
@@ -641,7 +863,7 @@
641 html.removeListener( 'mousemove', onHover ); 863 html.removeListener( 'mousemove', onHover );
642 removeListeners(); 864 removeListeners();
643 865
644 // Make it in effect on mouse up. (#9022) 866 // Make it in effect on mouse up. (http://dev.ckeditor.com/ticket/9022)
645 textRng.select(); 867 textRng.select();
646 } 868 }
647 869
@@ -652,11 +874,11 @@
652 evt.$.y < html.$.clientHeight && 874 evt.$.y < html.$.clientHeight &&
653 evt.$.x < html.$.clientWidth ) { 875 evt.$.x < html.$.clientWidth ) {
654 // Start to build the text range. 876 // Start to build the text range.
655 var textRng = body.$.createTextRange(); 877 textRng = body.$.createTextRange();
656 moveRangeToPoint( textRng, evt.$.clientX, evt.$.clientY ); 878 moveRangeToPoint( textRng, evt.$.clientX, evt.$.clientY );
657 879
658 // Records the dragging start of the above text range. 880 // Records the dragging start of the above text range.
659 var startRng = textRng.duplicate(); 881 startRng = textRng.duplicate();
660 882
661 html.on( 'mousemove', onHover ); 883 html.on( 'mousemove', onHover );
662 outerDoc.on( 'mouseup', onSelectEnd ); 884 outerDoc.on( 'mouseup', onSelectEnd );
@@ -670,7 +892,7 @@
670 if ( CKEDITOR.env.version > 7 && CKEDITOR.env.version < 11 ) { 892 if ( CKEDITOR.env.version > 7 && CKEDITOR.env.version < 11 ) {
671 html.on( 'mousedown', function( evt ) { 893 html.on( 'mousedown', function( evt ) {
672 if ( evt.data.getTarget().is( 'html' ) ) { 894 if ( evt.data.getTarget().is( 'html' ) ) {
673 // Limit the text selection mouse move inside of editable. (#9715) 895 // Limit the text selection mouse move inside of editable. (http://dev.ckeditor.com/ticket/9715)
674 outerDoc.on( 'mouseup', onSelectEnd ); 896 outerDoc.on( 'mouseup', onSelectEnd );
675 html.on( 'mouseup', onSelectEnd ); 897 html.on( 'mouseup', onSelectEnd );
676 } 898 }
@@ -684,6 +906,14 @@
684 // 2. After the accomplish of keyboard and mouse events. 906 // 2. After the accomplish of keyboard and mouse events.
685 editable.attachListener( editable, 'selectionchange', checkSelectionChange, editor ); 907 editable.attachListener( editable, 'selectionchange', checkSelectionChange, editor );
686 editable.attachListener( editable, 'keyup', checkSelectionChangeTimeout, editor ); 908 editable.attachListener( editable, 'keyup', checkSelectionChangeTimeout, editor );
909 // http://dev.ckeditor.com/ticket/14407 - Don't even let anything happen if the selection is in a non-editable element.
910 editable.attachListener( editable, 'keydown', function( evt ) {
911 var sel = this.getSelection( 1 );
912 if ( nonEditableAscendant( sel ) ) {
913 sel.selectElement( nonEditableAscendant( sel ) );
914 evt.data.preventDefault();
915 }
916 }, editor );
687 // Always fire the selection change on focus gain. 917 // Always fire the selection change on focus gain.
688 // On Webkit do this on DOMFocusIn, because the selection is unlocked on it too and 918 // On Webkit do this on DOMFocusIn, because the selection is unlocked on it too and
689 // we need synchronization between those listeners to not lost cached editor._.previousActive property 919 // we need synchronization between those listeners to not lost cached editor._.previousActive property
@@ -693,7 +923,7 @@
693 editor.selectionChange( 1 ); 923 editor.selectionChange( 1 );
694 } ); 924 } );
695 925
696 // #9699: On Webkit&Gecko in inline editor we have to check selection when it was changed 926 // http://dev.ckeditor.com/ticket/9699: On Webkit&Gecko in inline editor we have to check selection when it was changed
697 // by dragging and releasing mouse button outside editable. Dragging (mousedown) 927 // by dragging and releasing mouse button outside editable. Dragging (mousedown)
698 // has to be initialized in editable, but for mouseup we listen on document element. 928 // has to be initialized in editable, but for mouseup we listen on document element.
699 if ( isInline && ( CKEDITOR.env.webkit || CKEDITOR.env.gecko ) ) { 929 if ( isInline && ( CKEDITOR.env.webkit || CKEDITOR.env.gecko ) ) {
@@ -707,11 +937,11 @@
707 mouseDown = 0; 937 mouseDown = 0;
708 } ); 938 } );
709 } 939 }
710 // In all other cases listen on simple mouseup over editable, as we did before #9699. 940 // In all other cases listen on simple mouseup over editable, as we did before http://dev.ckeditor.com/ticket/9699.
711 // 941 //
712 // Use document instead of editable in non-IEs for observing mouseup 942 // Use document instead of editable in non-IEs for observing mouseup
713 // since editable won't fire the event if selection process started within iframe and ended out 943 // since editable won't fire the event if selection process started within iframe and ended out
714 // of the editor (#9851). 944 // of the editor (http://dev.ckeditor.com/ticket/9851).
715 else { 945 else {
716 editable.attachListener( CKEDITOR.env.ie ? editable : doc.getDocumentElement(), 'mouseup', checkSelectionChangeTimeout, editor ); 946 editable.attachListener( CKEDITOR.env.ie ? editable : doc.getDocumentElement(), 'mouseup', checkSelectionChangeTimeout, editor );
717 } 947 }
@@ -733,18 +963,18 @@
733 case 8: // BACKSPACE 963 case 8: // BACKSPACE
734 case 45: // INS 964 case 45: // INS
735 case 46: // DEl 965 case 46: // DEl
736 removeFillingCharSequenceNode( editable ); 966 if ( editable.hasFocus ) {
967 removeFillingCharSequenceNode( editable );
968 }
737 } 969 }
738 970
739 }, null, null, -1 ); 971 }, null, null, -1 );
740 } 972 }
741 973
742 // Automatically select non-editable element when navigating into
743 // it by left/right or backspace/del keys.
744 editable.attachListener( editable, 'keydown', getOnKeyDownListener( editor ), null, null, -1 ); 974 editable.attachListener( editable, 'keydown', getOnKeyDownListener( editor ), null, null, -1 );
745 975
746 function moveRangeToPoint( range, x, y ) { 976 function moveRangeToPoint( range, x, y ) {
747 // Error prune in IE7. (#9034, #9110) 977 // Error prune in IE7. (http://dev.ckeditor.com/ticket/9034, http://dev.ckeditor.com/ticket/9110)
748 try { 978 try {
749 range.moveToPoint( x, y ); 979 range.moveToPoint( x, y );
750 } catch ( e ) {} 980 } catch ( e ) {}
@@ -765,14 +995,27 @@
765 range = sel.createRange(); 995 range = sel.createRange();
766 996
767 // The selection range is reported on host, but actually it should applies to the content doc. 997 // The selection range is reported on host, but actually it should applies to the content doc.
768 if ( sel.type != 'None' && range.parentElement().ownerDocument == doc.$ ) 998 // The parentElement may be null for read only mode in IE10 and below (http://dev.ckeditor.com/ticket/9780).
999 if ( sel.type != 'None' && range.parentElement() && range.parentElement().ownerDocument == doc.$ )
769 range.select(); 1000 range.select();
770 } 1001 }
1002
1003 function nonEditableAscendant( sel ) {
1004 if ( CKEDITOR.env.ie ) {
1005 var range = sel.getRanges()[ 0 ],
1006 ascendant = range ? range.startContainer.getAscendant( function( parent ) {
1007 return parent.type == CKEDITOR.NODE_ELEMENT &&
1008 ( parent.getAttribute( 'contenteditable' ) == 'false' || parent.getAttribute( 'contenteditable' ) == 'true' );
1009 }, true ) : null ;
1010
1011 return range && ascendant.getAttribute( 'contenteditable' ) == 'false' && ascendant;
1012 }
1013 }
771 } ); 1014 } );
772 1015
773 editor.on( 'setData', function() { 1016 editor.on( 'setData', function() {
774 // Invalidate locked selection when unloading DOM. 1017 // Invalidate locked selection when unloading DOM.
775 // (#9521, #5217#comment:32 and #11500#comment:11) 1018 // (http://dev.ckeditor.com/ticket/9521, http://dev.ckeditor.com/ticket/5217#comment:32 and http://dev.ckeditor.com/ticket/11500#comment:11)
776 editor.unlockSelection(); 1019 editor.unlockSelection();
777 1020
778 // Webkit's selection will mess up after the data loading. 1021 // Webkit's selection will mess up after the data loading.
@@ -786,7 +1029,7 @@
786 editor.unlockSelection(); 1029 editor.unlockSelection();
787 } ); 1030 } );
788 1031
789 // IE9 might cease to work if there's an object selection inside the iframe (#7639). 1032 // IE9 might cease to work if there's an object selection inside the iframe (http://dev.ckeditor.com/ticket/7639).
790 if ( CKEDITOR.env.ie9Compat ) 1033 if ( CKEDITOR.env.ie9Compat )
791 editor.on( 'beforeDestroy', clearSelection, null, null, 9 ); 1034 editor.on( 'beforeDestroy', clearSelection, null, null, 9 );
792 1035
@@ -802,7 +1045,7 @@
802 // When loaded data are ready check whether hidden selection container was not loaded. 1045 // When loaded data are ready check whether hidden selection container was not loaded.
803 editor.on( 'loadSnapshot', function() { 1046 editor.on( 'loadSnapshot', function() {
804 var isElement = CKEDITOR.dom.walker.nodeType( CKEDITOR.NODE_ELEMENT ), 1047 var isElement = CKEDITOR.dom.walker.nodeType( CKEDITOR.NODE_ELEMENT ),
805 // TODO replace with el.find() which will be introduced in #9764, 1048 // TODO replace with el.find() which will be introduced in http://dev.ckeditor.com/ticket/9764,
806 // because it may happen that hidden sel container won't be the last element. 1049 // because it may happen that hidden sel container won't be the last element.
807 last = editor.editable().getLast( isElement ); 1050 last = editor.editable().getLast( isElement );
808 1051
@@ -849,7 +1092,7 @@
849 } ); 1092 } );
850 1093
851 // On WebKit only, we need a special "filling" char on some situations 1094 // On WebKit only, we need a special "filling" char on some situations
852 // (#1272). Here we set the events that should invalidate that char. 1095 // (http://dev.ckeditor.com/ticket/1272). Here we set the events that should invalidate that char.
853 if ( CKEDITOR.env.webkit ) { 1096 if ( CKEDITOR.env.webkit ) {
854 CKEDITOR.on( 'instanceReady', function( evt ) { 1097 CKEDITOR.on( 'instanceReady', function( evt ) {
855 var editor = evt.editor; 1098 var editor = evt.editor;
@@ -864,7 +1107,7 @@
864 1107
865 // Filter Undo snapshot's HTML to get rid of Filling Char Sequence. 1108 // Filter Undo snapshot's HTML to get rid of Filling Char Sequence.
866 // Note: CKEDITOR.dom.range.createBookmark2() normalizes snapshot's 1109 // Note: CKEDITOR.dom.range.createBookmark2() normalizes snapshot's
867 // bookmarks to anticipate the removal of FCSeq from the snapshot's HTML (#13816). 1110 // bookmarks to anticipate the removal of FCSeq from the snapshot's HTML (http://dev.ckeditor.com/ticket/13816).
868 editor.on( 'getSnapshot', function( evt ) { 1111 editor.on( 'getSnapshot', function( evt ) {
869 if ( evt.data ) { 1112 if ( evt.data ) {
870 evt.data = removeFillingCharSequenceString( evt.data ); 1113 evt.data = removeFillingCharSequenceString( evt.data );
@@ -873,7 +1116,7 @@
873 1116
874 // Filter data to get rid of Filling Char Sequence. Filter on #toDataFormat 1117 // Filter data to get rid of Filling Char Sequence. Filter on #toDataFormat
875 // instead of #getData because once removed, FCSeq may leave an empty element, 1118 // instead of #getData because once removed, FCSeq may leave an empty element,
876 // which should be pruned by the dataProcessor (#13816). 1119 // which should be pruned by the dataProcessor (http://dev.ckeditor.com/ticket/13816).
877 // Note: Used low priority to filter when dataProcessor works on strings, 1120 // Note: Used low priority to filter when dataProcessor works on strings,
878 // not pseudo–DOM. 1121 // not pseudo–DOM.
879 editor.on( 'toDataFormat', function( evt ) { 1122 editor.on( 'toDataFormat', function( evt ) {
@@ -1039,9 +1282,6 @@
1039 */ 1282 */
1040 CKEDITOR.SELECTION_ELEMENT = 3; 1283 CKEDITOR.SELECTION_ELEMENT = 3;
1041 1284
1042 var isMSSelection = typeof window.getSelection != 'function',
1043 nextRev = 1;
1044
1045 /** 1285 /**
1046 * Manipulates the selection within a DOM element. If the current browser selection 1286 * Manipulates the selection within a DOM element. If the current browser selection
1047 * spans outside of the element, an empty selection object is returned. 1287 * spans outside of the element, an empty selection object is returned.
@@ -1121,7 +1361,7 @@
1121 1361
1122 // Selection out of concerned range, empty the selection. 1362 // Selection out of concerned range, empty the selection.
1123 // TODO check whether this condition cannot be reverted to its old 1363 // TODO check whether this condition cannot be reverted to its old
1124 // form (commented out) after we closed #10438. 1364 // form (commented out) after we closed http://dev.ckeditor.com/ticket/10438.
1125 //if ( !( rangeParent && ( root.equals( rangeParent ) || root.contains( rangeParent ) ) ) ) { 1365 //if ( !( rangeParent && ( root.equals( rangeParent ) || root.contains( rangeParent ) ) ) ) {
1126 if ( !( 1366 if ( !(
1127 rangeParent && 1367 rangeParent &&
@@ -1142,10 +1382,6 @@
1142 var styleObjectElements = { img: 1, hr: 1, li: 1, table: 1, tr: 1, td: 1, th: 1, embed: 1, object: 1, ol: 1, ul: 1, 1382 var styleObjectElements = { img: 1, hr: 1, li: 1, table: 1, tr: 1, td: 1, th: 1, embed: 1, object: 1, ol: 1, ul: 1,
1143 a: 1, input: 1, form: 1, select: 1, textarea: 1, button: 1, fieldset: 1, thead: 1, tfoot: 1 }; 1383 a: 1, input: 1, form: 1, select: 1, textarea: 1, button: 1, fieldset: 1, thead: 1, tfoot: 1 };
1144 1384
1145 // #13816
1146 var fillingCharSequence = CKEDITOR.tools.repeat( '\u200b', 7 ),
1147 fillingCharSequenceRegExp = new RegExp( fillingCharSequence + '( )?', 'g' );
1148
1149 CKEDITOR.tools.extend( CKEDITOR.dom.selection, { 1385 CKEDITOR.tools.extend( CKEDITOR.dom.selection, {
1150 _removeFillingCharSequenceString: removeFillingCharSequenceString, 1386 _removeFillingCharSequenceString: removeFillingCharSequenceString,
1151 _createFillingCharSequenceNode: createFillingCharSequenceNode, 1387 _createFillingCharSequenceNode: createFillingCharSequenceNode,
@@ -1167,7 +1403,7 @@
1167 * 1403 *
1168 * var selection = editor.getSelection().getNative(); 1404 * var selection = editor.getSelection().getNative();
1169 * 1405 *
1170 * @returns {Object} The native browser selection object. 1406 * @returns {Object} The native browser selection object or null if this is a fake selection.
1171 */ 1407 */
1172 getNative: function() { 1408 getNative: function() {
1173 if ( this._.cache.nativeSel !== undefined ) 1409 if ( this._.cache.nativeSel !== undefined )
@@ -1259,7 +1495,7 @@
1259 * alert( ranges.length ); 1495 * alert( ranges.length );
1260 * 1496 *
1261 * @method 1497 * @method
1262 * @param {Boolean} [onlyEditables] If set to `true`, this function retrives editable ranges only. 1498 * @param {Boolean} [onlyEditables] If set to `true`, this function retrieves editable ranges only.
1263 * @returns {Array} Range instances that represent the current selection. 1499 * @returns {Array} Range instances that represent the current selection.
1264 */ 1500 */
1265 getRanges: ( function() { 1501 getRanges: ( function() {
@@ -1290,7 +1526,7 @@
1290 index = -1, 1526 index = -1,
1291 position, distance, container; 1527 position, distance, container;
1292 1528
1293 // Binary search over all element childs to test the range to see whether 1529 // Binary search over all element children to test the range to see whether
1294 // range is right on the boundary of one element. 1530 // range is right on the boundary of one element.
1295 while ( startIndex <= endIndex ) { 1531 while ( startIndex <= endIndex ) {
1296 index = Math.floor( ( startIndex + endIndex ) / 2 ); 1532 index = Math.floor( ( startIndex + endIndex ) / 2 );
@@ -1306,8 +1542,8 @@
1306 return { container: parent, offset: getNodeIndex( child ) }; 1542 return { container: parent, offset: getNodeIndex( child ) };
1307 } 1543 }
1308 1544
1309 // All childs are text nodes, 1545 // All children are text nodes,
1310 // or to the right hand of test range are all text nodes. (#6992) 1546 // or to the right hand of test range are all text nodes. (http://dev.ckeditor.com/ticket/6992)
1311 if ( index == -1 || index == siblings.length - 1 && position < 0 ) { 1547 if ( index == -1 || index == siblings.length - 1 && position < 0 ) {
1312 // Adapt test range to embrace the entire parent contents. 1548 // Adapt test range to embrace the entire parent contents.
1313 testRange.moveToElementText( parent ); 1549 testRange.moveToElementText( parent );
@@ -1315,7 +1551,7 @@
1315 1551
1316 // IE report line break as CRLF with range.text but 1552 // IE report line break as CRLF with range.text but
1317 // only LF with textnode.nodeValue, normalize them to avoid 1553 // only LF with textnode.nodeValue, normalize them to avoid
1318 // breaking character counting logic below. (#3949) 1554 // breaking character counting logic below. (http://dev.ckeditor.com/ticket/3949)
1319 distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length; 1555 distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length;
1320 1556
1321 siblings = parent.childNodes; 1557 siblings = parent.childNodes;
@@ -1351,7 +1587,7 @@
1351 1587
1352 // IE report line break as CRLF with range.text but 1588 // IE report line break as CRLF with range.text but
1353 // only LF with textnode.nodeValue, normalize them to avoid 1589 // only LF with textnode.nodeValue, normalize them to avoid
1354 // breaking character counting logic below. (#3949) 1590 // breaking character counting logic below. (http://dev.ckeditor.com/ticket/3949)
1355 distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length; 1591 distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length;
1356 1592
1357 // Actual range anchor right beside test range at the inner boundary of text node. 1593 // Actual range anchor right beside test range at the inner boundary of text node.
@@ -1368,7 +1604,7 @@
1368 } 1604 }
1369 child = sibling; 1605 child = sibling;
1370 } 1606 }
1371 // Measurement in IE could be somtimes wrong because of <select> element. (#4611) 1607 // Measurement in IE could be sometimes wrong because of <select> element. (http://dev.ckeditor.com/ticket/4611)
1372 catch ( e ) { 1608 catch ( e ) {
1373 return { container: parent, offset: getNodeIndex( child ) }; 1609 return { container: parent, offset: getNodeIndex( child ) };
1374 } 1610 }
@@ -1400,7 +1636,7 @@
1400 boundaryInfo = getBoundaryInformation( nativeRange ); 1636 boundaryInfo = getBoundaryInformation( nativeRange );
1401 range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset ); 1637 range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset );
1402 1638
1403 // Correct an invalid IE range case on empty list item. (#5850) 1639 // Correct an invalid IE range case on empty list item. (http://dev.ckeditor.com/ticket/5850)
1404 if ( range.endContainer.getPosition( range.startContainer ) & CKEDITOR.POSITION_PRECEDING && range.endOffset <= range.startContainer.getIndex() ) 1640 if ( range.endContainer.getPosition( range.startContainer ) & CKEDITOR.POSITION_PRECEDING && range.endOffset <= range.startContainer.getIndex() )
1405 range.collapse(); 1641 range.collapse();
1406 1642
@@ -1432,7 +1668,7 @@
1432 } )() : 1668 } )() :
1433 function() { 1669 function() {
1434 // On browsers implementing the W3C range, we simply 1670 // On browsers implementing the W3C range, we simply
1435 // tranform the native ranges in CKEDITOR.dom.range 1671 // transform the native ranges in CKEDITOR.dom.range
1436 // instances. 1672 // instances.
1437 1673
1438 var ranges = [], 1674 var ranges = [],
@@ -1465,7 +1701,7 @@
1465 return ranges; 1701 return ranges;
1466 1702
1467 // Split range into multiple by read-only nodes. 1703 // Split range into multiple by read-only nodes.
1468 // Clone ranges array to avoid changing cached ranges (#11493). 1704 // Clone ranges array to avoid changing cached ranges (http://dev.ckeditor.com/ticket/11493).
1469 return extractEditableRanges( new CKEDITOR.dom.rangeList( ranges.slice() ) ); 1705 return extractEditableRanges( new CKEDITOR.dom.rangeList( ranges.slice() ) );
1470 }; 1706 };
1471 } )(), 1707 } )(),
@@ -1499,11 +1735,11 @@
1499 1735
1500 // Decrease the range content to exclude particial 1736 // Decrease the range content to exclude particial
1501 // selected node on the start which doesn't have 1737 // selected node on the start which doesn't have
1502 // visual impact. ( #3231 ) 1738 // visual impact. ( http://dev.ckeditor.com/ticket/3231 )
1503 while ( 1 ) { 1739 while ( 1 ) {
1504 var startContainer = range.startContainer, 1740 var startContainer = range.startContainer,
1505 startOffset = range.startOffset; 1741 startOffset = range.startOffset;
1506 // Limit the fix only to non-block elements.(#3950) 1742 // Limit the fix only to non-block elements.(http://dev.ckeditor.com/ticket/3950)
1507 if ( startOffset == ( startContainer.getChildCount ? startContainer.getChildCount() : startContainer.getLength() ) && !startContainer.isBlockBoundary() ) 1743 if ( startOffset == ( startContainer.getChildCount ? startContainer.getChildCount() : startContainer.getLength() ) && !startContainer.isBlockBoundary() )
1508 range.setStartAfter( startContainer ); 1744 range.setStartAfter( startContainer );
1509 else 1745 else
@@ -1545,7 +1781,7 @@
1545 * var element = editor.getSelection().getSelectedElement(); 1781 * var element = editor.getSelection().getSelectedElement();
1546 * alert( element.getName() ); 1782 * alert( element.getName() );
1547 * 1783 *
1548 * @returns {CKEDITOR.dom.element} The selected element. Null if no 1784 * @returns {CKEDITOR.dom.element/null} The selected element. `null` if no
1549 * selection is available or the selection type is not {@link CKEDITOR#SELECTION_ELEMENT}. 1785 * selection is available or the selection type is not {@link CKEDITOR#SELECTION_ELEMENT}.
1550 */ 1786 */
1551 getSelectedElement: function() { 1787 getSelectedElement: function() {
@@ -1628,7 +1864,7 @@
1628 1864
1629 if ( restore ) { 1865 if ( restore ) {
1630 var selectedElement = this.getSelectedElement(), 1866 var selectedElement = this.getSelectedElement(),
1631 ranges = !selectedElement && this.getRanges(), 1867 ranges = this.getRanges(),
1632 faked = this.isFake; 1868 faked = this.isFake;
1633 } 1869 }
1634 1870
@@ -1642,7 +1878,10 @@
1642 if ( !( common && common.getAscendant( 'body', 1 ) ) ) 1878 if ( !( common && common.getAscendant( 'body', 1 ) ) )
1643 return; 1879 return;
1644 1880
1645 if ( faked ) 1881 if ( isTableSelection( ranges ) ) {
1882 // Tables have it's own selection method.
1883 performFakeTableSelection.call( this, ranges );
1884 } else if ( faked )
1646 this.fake( selectedElement ); 1885 this.fake( selectedElement );
1647 else if ( selectedElement ) 1886 else if ( selectedElement )
1648 this.selectElement( selectedElement ); 1887 this.selectElement( selectedElement );
@@ -1714,7 +1953,7 @@
1714 // Check if there's a hiddenSelectionContainer in editable at some index. 1953 // Check if there's a hiddenSelectionContainer in editable at some index.
1715 // Some ranges may be anchored after the hiddenSelectionContainer and, 1954 // Some ranges may be anchored after the hiddenSelectionContainer and,
1716 // once the container is removed while resetting the selection, they 1955 // once the container is removed while resetting the selection, they
1717 // may need new endOffset (one element less within the range) (#11021 #11393). 1956 // may need new endOffset (one element less within the range) (http://dev.ckeditor.com/ticket/11021 http://dev.ckeditor.com/ticket/11393).
1718 if ( hadHiddenSelectionContainer ) 1957 if ( hadHiddenSelectionContainer )
1719 fixRangesAfterHiddenSelectionContainer( ranges, this.root ); 1958 fixRangesAfterHiddenSelectionContainer( ranges, this.root );
1720 1959
@@ -1742,6 +1981,15 @@
1742 return; 1981 return;
1743 } 1982 }
1744 1983
1984 // Handle special case - fake selection of table cells.
1985 if ( editor && editor.plugins.tableselection &&
1986 CKEDITOR.plugins.tableselection.isSupportedEnvironment &&
1987 isTableSelection( ranges ) && !isSelectingTable
1988 ) {
1989 performFakeTableSelection.call( this, ranges );
1990 return;
1991 }
1992
1745 if ( isMSSelection ) { 1993 if ( isMSSelection ) {
1746 var notWhitespaces = CKEDITOR.dom.walker.whitespaces( true ), 1994 var notWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),
1747 fillerTextRegex = /\ufeff|\u00a0/, 1995 fillerTextRegex = /\ufeff|\u00a0/,
@@ -1775,7 +2023,7 @@
1775 if ( range.startContainer.type == CKEDITOR.NODE_ELEMENT && range.startContainer.getName() in nonCells || 2023 if ( range.startContainer.type == CKEDITOR.NODE_ELEMENT && range.startContainer.getName() in nonCells ||
1776 range.endContainer.type == CKEDITOR.NODE_ELEMENT && range.endContainer.getName() in nonCells ) { 2024 range.endContainer.type == CKEDITOR.NODE_ELEMENT && range.endContainer.getName() in nonCells ) {
1777 range.shrink( CKEDITOR.NODE_ELEMENT, true ); 2025 range.shrink( CKEDITOR.NODE_ELEMENT, true );
1778 // The range might get collapsed (#7975). Update cached variable. 2026 // The range might get collapsed (http://dev.ckeditor.com/ticket/7975). Update cached variable.
1779 collapsed = range.collapsed; 2027 collapsed = range.collapsed;
1780 } 2028 }
1781 2029
@@ -1817,18 +2065,18 @@
1817 2065
1818 // Append a temporary <span>&#65279;</span> before the selection. 2066 // Append a temporary <span>&#65279;</span> before the selection.
1819 // This is needed to avoid IE destroying selections inside empty 2067 // This is needed to avoid IE destroying selections inside empty
1820 // inline elements, like <b></b> (#253). 2068 // inline elements, like <b></b> (http://dev.ckeditor.com/ticket/253).
1821 // It is also needed when placing the selection right after an inline 2069 // It is also needed when placing the selection right after an inline
1822 // element to avoid the selection moving inside of it. 2070 // element to avoid the selection moving inside of it.
1823 dummySpan = range.document.createElement( 'span' ); 2071 dummySpan = range.document.createElement( 'span' );
1824 dummySpan.setHtml( '&#65279;' ); // Zero Width No-Break Space (U+FEFF). See #1359. 2072 dummySpan.setHtml( '&#65279;' ); // Zero Width No-Break Space (U+FEFF). See http://dev.ckeditor.com/ticket/1359.
1825 dummySpan.insertBefore( startNode ); 2073 dummySpan.insertBefore( startNode );
1826 2074
1827 if ( isStartMarkerAlone ) { 2075 if ( isStartMarkerAlone ) {
1828 // To expand empty blocks or line spaces after <br>, we need 2076 // To expand empty blocks or line spaces after <br>, we need
1829 // instead to have any char, which will be later deleted using the 2077 // instead to have any char, which will be later deleted using the
1830 // selection. 2078 // selection.
1831 // \ufeff = Zero Width No-Break Space (U+FEFF). (#1359) 2079 // \ufeff = Zero Width No-Break Space (U+FEFF). (http://dev.ckeditor.com/ticket/1359)
1832 range.document.createText( '\ufeff' ).insertBefore( startNode ); 2080 range.document.createText( '\ufeff' ).insertBefore( startNode );
1833 } 2081 }
1834 } 2082 }
@@ -1860,7 +2108,7 @@
1860 } else { 2108 } else {
1861 var sel = this.getNative(); 2109 var sel = this.getNative();
1862 2110
1863 // getNative() returns null if iframe is "display:none" in FF. (#6577) 2111 // getNative() returns null if iframe is "display:none" in FF. (http://dev.ckeditor.com/ticket/6577)
1864 if ( !sel ) 2112 if ( !sel )
1865 return; 2113 return;
1866 2114
@@ -1876,7 +2124,7 @@
1876 between.setStart( left.endContainer, left.endOffset ); 2124 between.setStart( left.endContainer, left.endOffset );
1877 between.setEnd( right.startContainer, right.startOffset ); 2125 between.setEnd( right.startContainer, right.startOffset );
1878 2126
1879 // Don't confused by Firefox adjancent multi-ranges 2127 // Don't confused by Firefox adjacent multi-ranges
1880 // introduced by table cells selection. 2128 // introduced by table cells selection.
1881 if ( !between.collapsed ) { 2129 if ( !between.collapsed ) {
1882 between.shrink( CKEDITOR.NODE_ELEMENT, true ); 2130 between.shrink( CKEDITOR.NODE_ELEMENT, true );
@@ -1885,7 +2133,7 @@
1885 2133
1886 // The following cases has to be considered: 2134 // The following cases has to be considered:
1887 // 1. <span contenteditable="false">[placeholder]</span> 2135 // 1. <span contenteditable="false">[placeholder]</span>
1888 // 2. <input contenteditable="false" type="radio"/> (#6621) 2136 // 2. <input contenteditable="false" type="radio"/> (http://dev.ckeditor.com/ticket/6621)
1889 if ( ancestor.isReadOnly() || enclosed && enclosed.isReadOnly() ) { 2137 if ( ancestor.isReadOnly() || enclosed && enclosed.isReadOnly() ) {
1890 right.setStart( left.startContainer, left.startOffset ); 2138 right.setStart( left.startContainer, left.startOffset );
1891 ranges.splice( i--, 1 ); 2139 ranges.splice( i--, 1 );
@@ -1900,7 +2148,7 @@
1900 2148
1901 if ( range.collapsed && CKEDITOR.env.webkit && rangeRequiresFix( range ) ) { 2149 if ( range.collapsed && CKEDITOR.env.webkit && rangeRequiresFix( range ) ) {
1902 // Append a zero-width space so WebKit will not try to 2150 // Append a zero-width space so WebKit will not try to
1903 // move the selection by itself (#1272). 2151 // move the selection by itself (http://dev.ckeditor.com/ticket/1272).
1904 var fillingChar = createFillingCharSequenceNode( this.root ); 2152 var fillingChar = createFillingCharSequenceNode( this.root );
1905 range.insertNode( fillingChar ); 2153 range.insertNode( fillingChar );
1906 2154
@@ -1954,14 +2202,20 @@
1954 * displayed to the user. 2202 * displayed to the user.
1955 * 2203 *
1956 * @param {CKEDITOR.dom.element} element The element to be "selected". 2204 * @param {CKEDITOR.dom.element} element The element to be "selected".
2205 * @param {String} [ariaLabel] A string to be used by the screen reader to describe the selection.
1957 */ 2206 */
1958 fake: function( element ) { 2207 fake: function( element, ariaLabel ) {
1959 var editor = this.root.editor; 2208 var editor = this.root.editor;
1960 2209
2210 // Attempt to retrieve aria-label if possible (http://dev.ckeditor.com/ticket/14539).
2211 if ( ariaLabel === undefined && element.hasAttribute( 'aria-label' ) ) {
2212 ariaLabel = element.getAttribute( 'aria-label' );
2213 }
2214
1961 // Cleanup after previous selection - e.g. remove hidden sel container. 2215 // Cleanup after previous selection - e.g. remove hidden sel container.
1962 this.reset(); 2216 this.reset();
1963 2217
1964 hideSelection( editor ); 2218 hideSelection( editor, ariaLabel );
1965 2219
1966 // Set this value after executing hiseSelection, because it may 2220 // Set this value after executing hiseSelection, because it may
1967 // cause reset() which overwrites cache. 2221 // cause reset() which overwrites cache.
@@ -2012,6 +2266,38 @@
2012 }, 2266 },
2013 2267
2014 /** 2268 /**
2269 * Checks if the selection contains an HTML element inside a table.
2270 * Returns `false` for text selection inside a table (e.g. it will return `false`
2271 * for text selected in one cell).
2272 *
2273 * editor.getSelection().isInTable();
2274 *
2275 * @since 4.7.0
2276 * @param {Boolean} [allowPartialSelection=false] Whether a partial cell selection should be included.
2277 * Added in 4.7.2.
2278 * @returns {Boolean}
2279 */
2280 isInTable: function( allowPartialSelection ) {
2281 return isTableSelection( this.getRanges(), allowPartialSelection );
2282 },
2283
2284 /**
2285 * Checks if the selection contains only one range which is collapsed.
2286 *
2287 * if ( editor.getSelection().isCollapsed() ) {
2288 * // Do something when the selection is collapsed.
2289 * }
2290 *
2291 * @since 4.7.3
2292 * @returns {Boolean}
2293 */
2294 isCollapsed: function() {
2295 var ranges = this.getRanges();
2296
2297 return ranges.length === 1 && ranges[ 0 ].collapsed;
2298 },
2299
2300 /**
2015 * Creates a bookmark for each range of this selection (from {@link #getRanges}) 2301 * Creates a bookmark for each range of this selection (from {@link #getRanges})
2016 * by calling the {@link CKEDITOR.dom.range#createBookmark} method, 2302 * by calling the {@link CKEDITOR.dom.range#createBookmark} method,
2017 * with extra care taken to avoid interference among those ranges. The arguments 2303 * with extra care taken to avoid interference among those ranges. The arguments
@@ -2064,17 +2350,19 @@
2064 2350
2065 // It may happen that the content change during loading, before selection is set so bookmark leads to text node. 2351 // It may happen that the content change during loading, before selection is set so bookmark leads to text node.
2066 if ( bookmarks.isFake ) { 2352 if ( bookmarks.isFake ) {
2067 node = ranges[ 0 ].getEnclosedNode(); 2353 node = isTableSelection( ranges ) ? ranges[ 0 ]._getTableElement() : ranges[ 0 ].getEnclosedNode();
2354
2068 if ( !node || node.type != CKEDITOR.NODE_ELEMENT ) { 2355 if ( !node || node.type != CKEDITOR.NODE_ELEMENT ) {
2069 CKEDITOR.warn( 'selection-not-fake' ); 2356 CKEDITOR.warn( 'selection-not-fake' );
2070 bookmarks.isFake = 0; 2357 bookmarks.isFake = 0;
2071 } 2358 }
2072 } 2359 }
2073 2360
2074 if ( bookmarks.isFake ) 2361 if ( bookmarks.isFake && !isTableSelection( ranges ) ) {
2075 this.fake( node ); 2362 this.fake( node );
2076 else 2363 } else {
2077 this.selectRanges( ranges ); 2364 this.selectRanges( ranges );
2365 }
2078 2366
2079 return this; 2367 return this;
2080 }, 2368 },
@@ -2111,7 +2399,7 @@
2111 * Remove all the selection ranges from the document. 2399 * Remove all the selection ranges from the document.
2112 */ 2400 */
2113 removeAllRanges: function() { 2401 removeAllRanges: function() {
2114 // Don't clear selection outside this selection's root (#11500). 2402 // Don't clear selection outside this selection's root (http://dev.ckeditor.com/ticket/11500).
2115 if ( this.getType() == CKEDITOR.SELECTION_NONE ) 2403 if ( this.getType() == CKEDITOR.SELECTION_NONE )
2116 return; 2404 return;
2117 2405
diff --git a/sources/core/skin.js b/sources/core/skin.js
index 98b8536..290157d 100644
--- a/sources/core/skin.js
+++ b/sources/core/skin.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
@@ -125,7 +125,7 @@
125 offset = overrideOffset || ( icon && icon.offset ); 125 offset = overrideOffset || ( icon && icon.offset );
126 bgsize = overrideBgsize || ( icon && icon.bgsize ) || '16px'; 126 bgsize = overrideBgsize || ( icon && icon.bgsize ) || '16px';
127 127
128 // If we use apostrophes in background-image, we must escape apostrophes in path (just to be sure). (#13361) 128 // If we use apostrophes in background-image, we must escape apostrophes in path (just to be sure). (http://dev.ckeditor.com/ticket/13361)
129 if ( path ) 129 if ( path )
130 path = path.replace( /'/g, '\\\'' ); 130 path = path.replace( /'/g, '\\\'' );
131 131
diff --git a/sources/core/style.js b/sources/core/style.js
index 09b117b..b3cf0bc 100644
--- a/sources/core/style.js
+++ b/sources/core/style.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
@@ -235,7 +235,7 @@ CKEDITOR.STYLE_OBJECT = 3;
235 var initialEnterMode = this._.enterMode; 235 var initialEnterMode = this._.enterMode;
236 236
237 // Before CKEditor 4.4 style knew nothing about editor, so in order to provide enterMode 237 // Before CKEditor 4.4 style knew nothing about editor, so in order to provide enterMode
238 // which should be used developers were forced to hack the style object (see #10190). 238 // which should be used developers were forced to hack the style object (see http://dev.ckeditor.com/ticket/10190).
239 // Since CKEditor 4.4 style knows about editor (at least when it's being applied/removed), but we 239 // Since CKEditor 4.4 style knows about editor (at least when it's being applied/removed), but we
240 // use _.enterMode for backward compatibility with those hacks. 240 // use _.enterMode for backward compatibility with those hacks.
241 // Note: we should not change style's enter mode if it was already set. 241 // Note: we should not change style's enter mode if it was already set.
@@ -569,7 +569,7 @@ CKEDITOR.STYLE_OBJECT = 3;
569 var styleVal = stylesDef[ style ], 569 var styleVal = stylesDef[ style ],
570 text = ( style + ':' + styleVal ).replace( semicolonFixRegex, ';' ); 570 text = ( style + ':' + styleVal ).replace( semicolonFixRegex, ';' );
571 571
572 // Some browsers don't support 'inherit' property value, leave them intact. (#5242) 572 // Some browsers don't support 'inherit' property value, leave them intact. (http://dev.ckeditor.com/ticket/5242)
573 if ( styleVal == 'inherit' ) 573 if ( styleVal == 'inherit' )
574 specialStylesText += text; 574 specialStylesText += text;
575 else 575 else
@@ -1024,7 +1024,7 @@ CKEDITOR.STYLE_OBJECT = 3;
1024 if ( !CKEDITOR.env.ie ) 1024 if ( !CKEDITOR.env.ie )
1025 styleNode.$.normalize(); 1025 styleNode.$.normalize();
1026 } 1026 }
1027 // Style already inherit from parents, left just to clear up any internal overrides. (#5931) 1027 // Style already inherit from parents, left just to clear up any internal overrides. (http://dev.ckeditor.com/ticket/5931)
1028 else { 1028 else {
1029 styleNode = new CKEDITOR.dom.element( 'span' ); 1029 styleNode = new CKEDITOR.dom.element( 'span' );
1030 styleRange.extractContents().appendTo( styleNode ); 1030 styleRange.extractContents().appendTo( styleNode );
@@ -1042,7 +1042,7 @@ CKEDITOR.STYLE_OBJECT = 3;
1042 // Remove the bookmark nodes. 1042 // Remove the bookmark nodes.
1043 range.moveToBookmark( boundaryNodes ); 1043 range.moveToBookmark( boundaryNodes );
1044 1044
1045 // Minimize the result range to exclude empty text nodes. (#5374) 1045 // Minimize the result range to exclude empty text nodes. (http://dev.ckeditor.com/ticket/5374)
1046 range.shrink( CKEDITOR.SHRINK_TEXT ); 1046 range.shrink( CKEDITOR.SHRINK_TEXT );
1047 1047
1048 // Get inside the remaining element if range.shrink( TEXT ) has failed because of non-editable elements inside. 1048 // Get inside the remaining element if range.shrink( TEXT ) has failed because of non-editable elements inside.
@@ -1058,27 +1058,31 @@ CKEDITOR.STYLE_OBJECT = 3;
1058 range.enlarge( CKEDITOR.ENLARGE_INLINE, 1 ); 1058 range.enlarge( CKEDITOR.ENLARGE_INLINE, 1 );
1059 1059
1060 var bookmark = range.createBookmark(), 1060 var bookmark = range.createBookmark(),
1061 startNode = bookmark.startNode; 1061 startNode = bookmark.startNode,
1062 alwaysRemoveElement = this._.definition.alwaysRemoveElement;
1062 1063
1063 if ( range.collapsed ) { 1064 if ( range.collapsed ) {
1064 var startPath = new CKEDITOR.dom.elementPath( startNode.getParent(), range.root ), 1065 var startPath = new CKEDITOR.dom.elementPath( startNode.getParent(), range.root ),
1065 // The topmost element in elementspatch which we should jump out of. 1066 // The topmost element in elements path which we should jump out of.
1066 boundaryElement; 1067 boundaryElement;
1067 1068
1068
1069 for ( var i = 0, element; i < startPath.elements.length && ( element = startPath.elements[ i ] ); i++ ) { 1069 for ( var i = 0, element; i < startPath.elements.length && ( element = startPath.elements[ i ] ); i++ ) {
1070 // 1. If it's collaped inside text nodes, try to remove the style from the whole element. 1070 // 1. If it's collaped inside text nodes, try to remove the style from the whole element.
1071 // 1071 //
1072 // 2. Otherwise if it's collapsed on element boundaries, moving the selection 1072 // 2. Otherwise if it's collapsed on element boundaries, moving the selection
1073 // outside the styles instead of removing the whole tag, 1073 // outside the styles instead of removing the whole tag,
1074 // also make sure other inner styles were well preserverd.(#3309) 1074 // also make sure other inner styles were well preserved.(http://dev.ckeditor.com/ticket/3309)
1075 if ( element == startPath.block || element == startPath.blockLimit ) 1075 //
1076 // 3. Force removing the element even if it's an boundary element when alwaysRemoveElement is true.
1077 // Without it, the links won't be unlinked if the cursor is placed right before/after it. (http://dev.ckeditor.com/ticket/13062)
1078 if ( element == startPath.block || element == startPath.blockLimit ) {
1076 break; 1079 break;
1080 }
1077 1081
1078 if ( this.checkElementRemovable( element ) ) { 1082 if ( this.checkElementRemovable( element ) ) {
1079 var isStart; 1083 var isStart;
1080 1084
1081 if ( range.collapsed && ( range.checkBoundaryOfElement( element, CKEDITOR.END ) || ( isStart = range.checkBoundaryOfElement( element, CKEDITOR.START ) ) ) ) { 1085 if ( !alwaysRemoveElement && range.collapsed && ( range.checkBoundaryOfElement( element, CKEDITOR.END ) || ( isStart = range.checkBoundaryOfElement( element, CKEDITOR.START ) ) ) ) {
1082 boundaryElement = element; 1086 boundaryElement = element;
1083 boundaryElement.match = isStart ? 'start' : 'end'; 1087 boundaryElement.match = isStart ? 'start' : 'end';
1084 } else { 1088 } else {
@@ -1087,10 +1091,11 @@ CKEDITOR.STYLE_OBJECT = 3;
1087 // no difference that they're separate entities in the DOM tree. So, merge 1091 // no difference that they're separate entities in the DOM tree. So, merge
1088 // them before removal. 1092 // them before removal.
1089 element.mergeSiblings(); 1093 element.mergeSiblings();
1090 if ( element.is( this.element ) ) 1094 if ( element.is( this.element ) ) {
1091 removeFromElement.call( this, element ); 1095 removeFromElement.call( this, element );
1092 else 1096 } else {
1093 removeOverrides( element, getOverrides( this )[ element.getName() ] ); 1097 removeOverrides( element, getOverrides( this )[ element.getName() ] );
1098 }
1094 } 1099 }
1095 } 1100 }
1096 } 1101 }
@@ -1235,7 +1240,7 @@ CKEDITOR.STYLE_OBJECT = 3;
1235 } 1240 }
1236 1241
1237 function applyObjectStyle( range ) { 1242 function applyObjectStyle( range ) {
1238 // Selected or parent element. (#9651) 1243 // Selected or parent element. (http://dev.ckeditor.com/ticket/9651)
1239 var start = range.getEnclosedNode() || range.getCommonAncestor( false, true ), 1244 var start = range.getEnclosedNode() || range.getCommonAncestor( false, true ),
1240 element = new CKEDITOR.dom.elementPath( start, range.root ).contains( this.element, 1 ); 1245 element = new CKEDITOR.dom.elementPath( start, range.root ).contains( this.element, 1 );
1241 1246
@@ -1276,7 +1281,7 @@ CKEDITOR.STYLE_OBJECT = 3;
1276 var iterator = range.createIterator(); 1281 var iterator = range.createIterator();
1277 iterator.enforceRealBlocks = true; 1282 iterator.enforceRealBlocks = true;
1278 1283
1279 // make recognize <br /> tag as a separator in ENTER_BR mode (#5121) 1284 // make recognize <br /> tag as a separator in ENTER_BR mode (http://dev.ckeditor.com/ticket/5121)
1280 if ( this._.enterMode ) 1285 if ( this._.enterMode )
1281 iterator.enlargeBr = ( this._.enterMode != CKEDITOR.ENTER_BR ); 1286 iterator.enlargeBr = ( this._.enterMode != CKEDITOR.ENTER_BR );
1282 1287
@@ -1326,7 +1331,7 @@ CKEDITOR.STYLE_OBJECT = 3;
1326 1331
1327 // Replace the original block with new one, with special treatment 1332 // Replace the original block with new one, with special treatment
1328 // for <pre> blocks to make sure content format is well preserved, and merging/splitting adjacent 1333 // for <pre> blocks to make sure content format is well preserved, and merging/splitting adjacent
1329 // when necessary. (#3188) 1334 // when necessary. (http://dev.ckeditor.com/ticket/3188)
1330 function replaceBlock( block, newBlock ) { 1335 function replaceBlock( block, newBlock ) {
1331 // Block is to be removed, create a temp element to 1336 // Block is to be removed, create a temp element to
1332 // save contents. 1337 // save contents.
@@ -1502,11 +1507,11 @@ CKEDITOR.STYLE_OBJECT = 3;
1502 1507
1503 // Remove definition attributes/style from the elemnt. 1508 // Remove definition attributes/style from the elemnt.
1504 for ( var attName in attributes ) { 1509 for ( var attName in attributes ) {
1505 // The 'class' element value must match (#1318). 1510 // The 'class' element value must match (http://dev.ckeditor.com/ticket/1318).
1506 if ( ( attName == 'class' || this._.definition.fullMatch ) && element.getAttribute( attName ) != normalizeProperty( attName, attributes[ attName ] ) ) 1511 if ( ( attName == 'class' || this._.definition.fullMatch ) && element.getAttribute( attName ) != normalizeProperty( attName, attributes[ attName ] ) )
1507 continue; 1512 continue;
1508 1513
1509 // Do not touch data-* attributes (#11011) (#11258). 1514 // Do not touch data-* attributes (http://dev.ckeditor.com/ticket/11011) (http://dev.ckeditor.com/ticket/11258).
1510 if ( keepDataAttrs && attName.slice( 0, 5 ) == 'data-' ) 1515 if ( keepDataAttrs && attName.slice( 0, 5 ) == 'data-' )
1511 continue; 1516 continue;
1512 1517
@@ -1515,7 +1520,7 @@ CKEDITOR.STYLE_OBJECT = 3;
1515 } 1520 }
1516 1521
1517 for ( var styleName in styles ) { 1522 for ( var styleName in styles ) {
1518 // Full match style insist on having fully equivalence. (#5018) 1523 // Full match style insist on having fully equivalence. (http://dev.ckeditor.com/ticket/5018)
1519 if ( this._.definition.fullMatch && element.getStyle( styleName ) != normalizeProperty( styleName, styles[ styleName ], true ) ) 1524 if ( this._.definition.fullMatch && element.getStyle( styleName ) != normalizeProperty( styleName, styles[ styleName ], true ) )
1520 continue; 1525 continue;
1521 1526
@@ -1649,7 +1654,7 @@ CKEDITOR.STYLE_OBJECT = 3;
1649 // Create the element. 1654 // Create the element.
1650 el = new CKEDITOR.dom.element( elementName, targetDocument ); 1655 el = new CKEDITOR.dom.element( elementName, targetDocument );
1651 1656
1652 // #6226: attributes should be copied before the new ones are applied 1657 // http://dev.ckeditor.com/ticket/6226: attributes should be copied before the new ones are applied
1653 if ( element ) 1658 if ( element )
1654 element.copyAttributes( el ); 1659 element.copyAttributes( el );
1655 1660
@@ -1794,15 +1799,28 @@ CKEDITOR.STYLE_OBJECT = 3;
1794 // is treated as a wildcard which will match any value. 1799 // is treated as a wildcard which will match any value.
1795 // @param {Object/String} source 1800 // @param {Object/String} source
1796 // @param {Object/String} target 1801 // @param {Object/String} target
1802 // @returns {Boolean}
1797 function compareCssText( source, target ) { 1803 function compareCssText( source, target ) {
1804 function filter( string, propertyName ) {
1805 // In case of font-families we'll skip quotes. (http://dev.ckeditor.com/ticket/10750)
1806 return propertyName.toLowerCase() == 'font-family' ? string.replace( /["']/g, '' ) : string;
1807 }
1808
1798 if ( typeof source == 'string' ) 1809 if ( typeof source == 'string' )
1799 source = CKEDITOR.tools.parseCssText( source ); 1810 source = CKEDITOR.tools.parseCssText( source );
1800 if ( typeof target == 'string' ) 1811 if ( typeof target == 'string' )
1801 target = CKEDITOR.tools.parseCssText( target, true ); 1812 target = CKEDITOR.tools.parseCssText( target, true );
1802 1813
1803 for ( var name in source ) { 1814 for ( var name in source ) {
1804 if ( !( name in target && ( target[ name ] == source[ name ] || source[ name ] == 'inherit' || target[ name ] == 'inherit' ) ) ) 1815 if ( !( name in target ) ) {
1805 return false; 1816 return false;
1817 }
1818
1819 if ( !( filter( target[ name ], name ) == filter( source[ name ], name ) ||
1820 source[ name ] == 'inherit' ||
1821 target[ name ] == 'inherit' ) ) {
1822 return false;
1823 }
1806 } 1824 }
1807 return true; 1825 return true;
1808 } 1826 }
@@ -1811,13 +1829,25 @@ CKEDITOR.STYLE_OBJECT = 3;
1811 var doc = selection.document, 1829 var doc = selection.document,
1812 ranges = selection.getRanges(), 1830 ranges = selection.getRanges(),
1813 func = remove ? this.removeFromRange : this.applyToRange, 1831 func = remove ? this.removeFromRange : this.applyToRange,
1814 range; 1832 originalRanges,
1833 range,
1834 i;
1835
1836 // In case of fake table selection, we would like to apply all styles and then select
1837 // the original ranges. Otherwise browsers would complain about discontiguous selection.
1838 if ( selection.isFake && selection.isInTable() ) {
1839 originalRanges = [];
1840
1841 for ( i = 0; i < ranges.length; i++ ) {
1842 originalRanges.push( ranges[ i ].clone() );
1843 }
1844 }
1815 1845
1816 var iterator = ranges.createIterator(); 1846 var iterator = ranges.createIterator();
1817 while ( ( range = iterator.getNextRange() ) ) 1847 while ( ( range = iterator.getNextRange() ) )
1818 func.call( this, range, editor ); 1848 func.call( this, range, editor );
1819 1849
1820 selection.selectRanges( ranges ); 1850 selection.selectRanges( originalRanges || ranges );
1821 doc.removeCustomData( 'doc_processing_style' ); 1851 doc.removeCustomData( 'doc_processing_style' );
1822 } 1852 }
1823} )(); 1853} )();
@@ -1888,7 +1918,7 @@ CKEDITOR.styleCommand.prototype.exec = function( editor ) {
1888 */ 1918 */
1889CKEDITOR.stylesSet = new CKEDITOR.resourceManager( '', 'stylesSet' ); 1919CKEDITOR.stylesSet = new CKEDITOR.resourceManager( '', 'stylesSet' );
1890 1920
1891// Backward compatibility (#5025). 1921// Backward compatibility (http://dev.ckeditor.com/ticket/5025).
1892CKEDITOR.addStylesSet = CKEDITOR.tools.bind( CKEDITOR.stylesSet.add, CKEDITOR.stylesSet ); 1922CKEDITOR.addStylesSet = CKEDITOR.tools.bind( CKEDITOR.stylesSet.add, CKEDITOR.stylesSet );
1893CKEDITOR.loadStylesSet = function( name, url, callback ) { 1923CKEDITOR.loadStylesSet = function( name, url, callback ) {
1894 CKEDITOR.stylesSet.addExternal( name, url, '' ); 1924 CKEDITOR.stylesSet.addExternal( name, url, '' );
@@ -1991,7 +2021,7 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype, {
1991 return; 2021 return;
1992 } 2022 }
1993 2023
1994 // #5352 Allow to define the styles directly in the config object 2024 // http://dev.ckeditor.com/ticket/5352 Allow to define the styles directly in the config object
1995 if ( configStyleSet instanceof Array ) { 2025 if ( configStyleSet instanceof Array ) {
1996 editor._.stylesDefinitions = configStyleSet; 2026 editor._.stylesDefinitions = configStyleSet;
1997 callback( configStyleSet ); 2027 callback( configStyleSet );
diff --git a/sources/core/template.js b/sources/core/template.js
index a3fe55b..a627c34 100644
--- a/sources/core/template.js
+++ b/sources/core/template.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
@@ -9,11 +9,7 @@
9 */ 9 */
10 10
11( function() { 11( function() {
12 var cache = {}, 12 var rePlaceholder = /{([^}]+)}/g;
13 rePlaceholder = /{([^}]+)}/g,
14 reEscapableChars = /([\\'])/g,
15 reNewLine = /\n/g,
16 reCarriageReturn = /\r/g;
17 13
18 /** 14 /**
19 * Lightweight template used to build the output string from variables. 15 * Lightweight template used to build the output string from variables.
@@ -27,42 +23,35 @@
27 * @param {String} source The template source. 23 * @param {String} source The template source.
28 */ 24 */
29 CKEDITOR.template = function( source ) { 25 CKEDITOR.template = function( source ) {
30 // Builds an optimized function body for the output() method, focused on performance. 26 /**
31 // For example, if we have this "source": 27 * The current template source.
32 // '<div style="{style}">{editorName}</div>' 28 *
33 // ... the resulting function body will be (apart from the "buffer" handling): 29 * @readonly
34 // return [ '<div style="', data['style'] == undefined ? '{style}' : data['style'], '">', data['editorName'] == undefined ? '{editorName}' : data['editorName'], '</div>' ].join(''); 30 * @member CKEDITOR.template
31 * @property {String}
32 */
33 this.source = String( source );
34 };
35 35
36 // Try to read from the cache. 36 /**
37 if ( cache[ source ] ) 37 * Processes the template, filling its variables with the provided data.
38 this.output = cache[ source ]; 38 *
39 else { 39 * @method
40 var fn = source 40 * @member CKEDITOR.template
41 // Escape chars like slash "\" or single quote "'". 41 * @param {Object} data An object containing properties whose values will be
42 .replace( reEscapableChars, '\\$1' ) 42 * used to fill the template variables. The property names must match the
43 .replace( reNewLine, '\\n' ) 43 * template variables names. Variables without matching properties will be
44 .replace( reCarriageReturn, '\\r' ) 44 * kept untouched.
45 // Inject the template keys replacement. 45 * @param {Array} [buffer] An array that the output data will be pushed into.
46 .replace( rePlaceholder, function( m, key ) { 46 * The number of entries appended to the array is unknown.
47 return "',data['" + key + "']==undefined?'{" + key + "}':data['" + key + "'],'"; 47 * @returns {String/Number} If `buffer` has not been provided, the processed
48 } ); 48 * template output data; otherwise the new length of `buffer`.
49 */
50 CKEDITOR.template.prototype.output = function( data, buffer ) {
51 var output = this.source.replace( rePlaceholder, function( fullMatch, dataKey ) {
52 return data[ dataKey ] !== undefined ? data[ dataKey ] : fullMatch;
53 } );
49 54
50 fn = "return buffer?buffer.push('" + fn + "'):['" + fn + "'].join('');"; 55 return buffer ? buffer.push( output ) : output;
51 this.output = cache[ source ] = Function( 'data', 'buffer', fn );
52 }
53 }; 56 };
54} )(); 57} )();
55
56/**
57 * Processes the template, filling its variables with the provided data.
58 *
59 * @method output
60 * @param {Object} data An object containing properties which values will be
61 * used to fill the template variables. The property names must match the
62 * template variables names. Variables without matching properties will be
63 * kept untouched.
64 * @param {Array} [buffer] An array into which the output data will be pushed into.
65 * The number of entries appended to the array is unknown.
66 * @returns {String/Number} If `buffer` has not been provided, the processed
67 * template output data, otherwise the new length of `buffer`.
68 */
diff --git a/sources/core/tools.js b/sources/core/tools.js
index ae5b4d0..a4b736d 100644
--- a/sources/core/tools.js
+++ b/sources/core/tools.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
@@ -981,6 +981,23 @@
981 }, 981 },
982 982
983 /** 983 /**
984 * Normalizes hexadecimal notation so that the color string is always 6 characters long and lowercase.
985 *
986 * @param {String} styleText The style data (or just a string containing hex colors) to be converted.
987 * @returns {String} The style data with hex colors normalized.
988 */
989 normalizeHex: function( styleText ) {
990 return styleText.replace( /#(([0-9a-f]{3}){1,2})($|;|\s+)/gi, function( match, hexColor, hexColorPart, separator ) {
991 var normalizedHexColor = hexColor.toLowerCase();
992 if ( normalizedHexColor.length == 3 ) {
993 var parts = normalizedHexColor.split( '' );
994 normalizedHexColor = [ parts[ 0 ], parts[ 0 ], parts[ 1 ], parts[ 1 ], parts[ 2 ], parts[ 2 ] ].join( '' );
995 }
996 return '#' + normalizedHexColor + separator;
997 } );
998 },
999
1000 /**
984 * Turns inline style text properties into one hash. 1001 * Turns inline style text properties into one hash.
985 * 1002 *
986 * @param {String} styleText The style data to be parsed. 1003 * @param {String} styleText The style data to be parsed.
@@ -996,21 +1013,24 @@
996 // Injects the style in a temporary span object, so the browser parses it, 1013 // Injects the style in a temporary span object, so the browser parses it,
997 // retrieving its final format. 1014 // retrieving its final format.
998 var temp = new CKEDITOR.dom.element( 'span' ); 1015 var temp = new CKEDITOR.dom.element( 'span' );
999 temp.setAttribute( 'style', styleText ); 1016 styleText = temp.setAttribute( 'style', styleText ).getAttribute( 'style' ) || '';
1000 styleText = CKEDITOR.tools.convertRgbToHex( temp.getAttribute( 'style' ) || '' ); 1017 }
1018
1019 // Normalize colors.
1020 if ( styleText ) {
1021 styleText = CKEDITOR.tools.normalizeHex( CKEDITOR.tools.convertRgbToHex( styleText ) );
1001 } 1022 }
1002 1023
1003 // IE will leave a single semicolon when failed to parse the style text. (#3891) 1024 // IE will leave a single semicolon when failed to parse the style text. (http://dev.ckeditor.com/ticket/3891)
1004 if ( !styleText || styleText == ';' ) 1025 if ( !styleText || styleText == ';' )
1005 return retval; 1026 return retval;
1006 1027
1007 styleText.replace( /&quot;/g, '"' ).replace( /\s*([^:;\s]+)\s*:\s*([^;]+)\s*(?=;|$)/g, function( match, name, value ) { 1028 styleText.replace( /&quot;/g, '"' ).replace( /\s*([^:;\s]+)\s*:\s*([^;]+)\s*(?=;|$)/g, function( match, name, value ) {
1008 if ( normalize ) { 1029 if ( normalize ) {
1009 name = name.toLowerCase(); 1030 name = name.toLowerCase();
1010 // Normalize font-family property, ignore quotes and being case insensitive. (#7322) 1031 // Drop extra whitespacing from font-family.
1011 // http://www.w3.org/TR/css3-fonts/#font-family-the-font-family-property
1012 if ( name == 'font-family' ) 1032 if ( name == 'font-family' )
1013 value = value.toLowerCase().replace( /["']/g, '' ).replace( /\s*,\s*/g, ',' ); 1033 value = value.replace( /\s*,\s*/g, ',' );
1014 value = CKEDITOR.tools.trim( value ); 1034 value = CKEDITOR.tools.trim( value );
1015 } 1035 }
1016 1036
@@ -1293,6 +1313,67 @@
1293 }, 1313 },
1294 1314
1295 /** 1315 /**
1316 * Converts a keystroke to its string representation. Returns an object with two fields:
1317 *
1318 * * `display` &ndash; A string that should be used for visible labels.
1319 * For Mac devices it uses `⌥` for `ALT`, `⇧` for `SHIFT` and `⌘` for `COMMAND`.
1320 * * `aria` &ndash; A string that should be used for ARIA descriptions.
1321 * It does not use special characters such as `⌥`, `⇧` or `⌘`.
1322 *
1323 * var lang = editor.lang.common.keyboard;
1324 * var shortcut = CKEDITOR.tools.keystrokeToString( lang, CKEDITOR.CTRL + 88 );
1325 * console.log( shortcut.display ); // 'CTRL + X', on Mac '⌘ + X'.
1326 * console.log( shortcut.aria ); // 'CTRL + X', on Mac 'COMMAND + X'.
1327 *
1328 * @since 4.6.0
1329 * @param {Object} lang A language object with the key name translation.
1330 * @param {Number} keystroke The keystroke to convert.
1331 * @returns {{display: String, aria: String}}
1332 */
1333 keystrokeToString: function( lang, keystroke ) {
1334 var special = keystroke & 0xFF0000,
1335 key = keystroke & 0x00FFFF,
1336 isMac = CKEDITOR.env.mac,
1337 CTRL = 17,
1338 CMD = 224,
1339 ALT = 18,
1340 SHIFT = 16,
1341 display = [],
1342 aria = [];
1343
1344
1345 if ( special & CKEDITOR.CTRL ) {
1346 display.push( isMac ? '⌘' : lang[ CTRL ] );
1347 aria.push( isMac ? lang[ CMD ] : lang[ CTRL ] );
1348 }
1349
1350 if ( special & CKEDITOR.ALT ) {
1351 display.push( isMac ? '⌥' : lang[ ALT ] );
1352 aria.push( lang[ ALT ] );
1353 }
1354
1355 if ( special & CKEDITOR.SHIFT ) {
1356 display.push( isMac ? '⇧' : lang[ SHIFT ] );
1357 aria.push( lang[ SHIFT ] );
1358 }
1359
1360 if ( key ) {
1361 if ( lang[ key ] ) {
1362 display.push( lang[ key ] );
1363 aria.push( lang[ key ] );
1364 } else {
1365 display.push( String.fromCharCode( key ) );
1366 aria.push( String.fromCharCode( key ) );
1367 }
1368 }
1369
1370 return {
1371 display: display.join( '+' ),
1372 aria: aria.join( '+' )
1373 };
1374 },
1375
1376 /**
1296 * The data URI of a transparent image. May be used e.g. in HTML as an image source or in CSS in `url()`. 1377 * The data URI of a transparent image. May be used e.g. in HTML as an image source or in CSS in `url()`.
1297 * 1378 *
1298 * @since 4.4 1379 * @since 4.4
@@ -1353,6 +1434,540 @@
1353 } 1434 }
1354 1435
1355 return token; 1436 return token;
1437 },
1438
1439 /**
1440 * Returns an escaped CSS selector. `CSS.escape()` is used if defined, leading digit is escaped otherwise.
1441 *
1442 * @since 4.5.10
1443 * @param {String} selector A CSS selector to escape.
1444 * @returns {String} An escaped selector.
1445 */
1446 escapeCss: function( selector ) {
1447 // Invalid input.
1448 if ( !selector ) {
1449 return '';
1450 }
1451
1452 // CSS.escape() can be used.
1453 if ( window.CSS && CSS.escape ) {
1454 return CSS.escape( selector );
1455 }
1456
1457 // Simple leading digit escape.
1458 if ( !isNaN( parseInt( selector.charAt( 0 ), 10 ) ) ) {
1459 return '\\3' + selector.charAt( 0 ) + ' ' + selector.substring( 1, selector.length );
1460 }
1461
1462 return selector;
1463 },
1464
1465 /**
1466 * Detects which mouse button generated a given DOM event.
1467 *
1468 * @since 4.7.3
1469 * @param {CKEDITOR.dom.event} evt DOM event.
1470 * @returns {Number|Boolean} Returns a number indicating the mouse button or `false`
1471 * if the mouse button cannot be determined.
1472 */
1473 getMouseButton: function( evt ) {
1474 var evtData = evt.data,
1475 domEvent = evtData && evtData.$;
1476
1477 if ( !( evtData && domEvent ) ) {
1478 // Added in case when there's no data available. That's the case in some unit test in built version which
1479 // mock event but doesn't put data object.
1480 return false;
1481 }
1482
1483 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) {
1484 if ( domEvent.button === 4 ) {
1485 return CKEDITOR.MOUSE_BUTTON_MIDDLE;
1486 } else if ( domEvent.button === 1 ) {
1487 return CKEDITOR.MOUSE_BUTTON_LEFT;
1488 } else {
1489 return CKEDITOR.MOUSE_BUTTON_RIGHT;
1490 }
1491 }
1492
1493 return domEvent.button;
1494 },
1495
1496 /**
1497 * A set of functions for operations on styles.
1498 *
1499 * @property {CKEDITOR.tools.style}
1500 */
1501 style: {
1502 /**
1503 * Methods to parse miscellaneous CSS properties.
1504 *
1505 * @property {CKEDITOR.tools.style.parse}
1506 * @member CKEDITOR.tools.style
1507 */
1508 parse: {
1509 // Color list based on https://www.w3.org/TR/css-color-4/#named-colors.
1510 _colors: {
1511 aliceblue: '#F0F8FF',
1512 antiquewhite: '#FAEBD7',
1513 aqua: '#00FFFF',
1514 aquamarine: '#7FFFD4',
1515 azure: '#F0FFFF',
1516 beige: '#F5F5DC',
1517 bisque: '#FFE4C4',
1518 black: '#000000',
1519 blanchedalmond: '#FFEBCD',
1520 blue: '#0000FF',
1521 blueviolet: '#8A2BE2',
1522 brown: '#A52A2A',
1523 burlywood: '#DEB887',
1524 cadetblue: '#5F9EA0',
1525 chartreuse: '#7FFF00',
1526 chocolate: '#D2691E',
1527 coral: '#FF7F50',
1528 cornflowerblue: '#6495ED',
1529 cornsilk: '#FFF8DC',
1530 crimson: '#DC143C',
1531 cyan: '#00FFFF',
1532 darkblue: '#00008B',
1533 darkcyan: '#008B8B',
1534 darkgoldenrod: '#B8860B',
1535 darkgray: '#A9A9A9',
1536 darkgreen: '#006400',
1537 darkgrey: '#A9A9A9',
1538 darkkhaki: '#BDB76B',
1539 darkmagenta: '#8B008B',
1540 darkolivegreen: '#556B2F',
1541 darkorange: '#FF8C00',
1542 darkorchid: '#9932CC',
1543 darkred: '#8B0000',
1544 darksalmon: '#E9967A',
1545 darkseagreen: '#8FBC8F',
1546 darkslateblue: '#483D8B',
1547 darkslategray: '#2F4F4F',
1548 darkslategrey: '#2F4F4F',
1549 darkturquoise: '#00CED1',
1550 darkviolet: '#9400D3',
1551 deeppink: '#FF1493',
1552 deepskyblue: '#00BFFF',
1553 dimgray: '#696969',
1554 dimgrey: '#696969',
1555 dodgerblue: '#1E90FF',
1556 firebrick: '#B22222',
1557 floralwhite: '#FFFAF0',
1558 forestgreen: '#228B22',
1559 fuchsia: '#FF00FF',
1560 gainsboro: '#DCDCDC',
1561 ghostwhite: '#F8F8FF',
1562 gold: '#FFD700',
1563 goldenrod: '#DAA520',
1564 gray: '#808080',
1565 green: '#008000',
1566 greenyellow: '#ADFF2F',
1567 grey: '#808080',
1568 honeydew: '#F0FFF0',
1569 hotpink: '#FF69B4',
1570 indianred: '#CD5C5C',
1571 indigo: '#4B0082',
1572 ivory: '#FFFFF0',
1573 khaki: '#F0E68C',
1574 lavender: '#E6E6FA',
1575 lavenderblush: '#FFF0F5',
1576 lawngreen: '#7CFC00',
1577 lemonchiffon: '#FFFACD',
1578 lightblue: '#ADD8E6',
1579 lightcoral: '#F08080',
1580 lightcyan: '#E0FFFF',
1581 lightgoldenrodyellow: '#FAFAD2',
1582 lightgray: '#D3D3D3',
1583 lightgreen: '#90EE90',
1584 lightgrey: '#D3D3D3',
1585 lightpink: '#FFB6C1',
1586 lightsalmon: '#FFA07A',
1587 lightseagreen: '#20B2AA',
1588 lightskyblue: '#87CEFA',
1589 lightslategray: '#778899',
1590 lightslategrey: '#778899',
1591 lightsteelblue: '#B0C4DE',
1592 lightyellow: '#FFFFE0',
1593 lime: '#00FF00',
1594 limegreen: '#32CD32',
1595 linen: '#FAF0E6',
1596 magenta: '#FF00FF',
1597 maroon: '#800000',
1598 mediumaquamarine: '#66CDAA',
1599 mediumblue: '#0000CD',
1600 mediumorchid: '#BA55D3',
1601 mediumpurple: '#9370DB',
1602 mediumseagreen: '#3CB371',
1603 mediumslateblue: '#7B68EE',
1604 mediumspringgreen: '#00FA9A',
1605 mediumturquoise: '#48D1CC',
1606 mediumvioletred: '#C71585',
1607 midnightblue: '#191970',
1608 mintcream: '#F5FFFA',
1609 mistyrose: '#FFE4E1',
1610 moccasin: '#FFE4B5',
1611 navajowhite: '#FFDEAD',
1612 navy: '#000080',
1613 oldlace: '#FDF5E6',
1614 olive: '#808000',
1615 olivedrab: '#6B8E23',
1616 orange: '#FFA500',
1617 orangered: '#FF4500',
1618 orchid: '#DA70D6',
1619 palegoldenrod: '#EEE8AA',
1620 palegreen: '#98FB98',
1621 paleturquoise: '#AFEEEE',
1622 palevioletred: '#DB7093',
1623 papayawhip: '#FFEFD5',
1624 peachpuff: '#FFDAB9',
1625 peru: '#CD853F',
1626 pink: '#FFC0CB',
1627 plum: '#DDA0DD',
1628 powderblue: '#B0E0E6',
1629 purple: '#800080',
1630 rebeccapurple: '#663399',
1631 red: '#FF0000',
1632 rosybrown: '#BC8F8F',
1633 royalblue: '#4169E1',
1634 saddlebrown: '#8B4513',
1635 salmon: '#FA8072',
1636 sandybrown: '#F4A460',
1637 seagreen: '#2E8B57',
1638 seashell: '#FFF5EE',
1639 sienna: '#A0522D',
1640 silver: '#C0C0C0',
1641 skyblue: '#87CEEB',
1642 slateblue: '#6A5ACD',
1643 slategray: '#708090',
1644 slategrey: '#708090',
1645 snow: '#FFFAFA',
1646 springgreen: '#00FF7F',
1647 steelblue: '#4682B4',
1648 tan: '#D2B48C',
1649 teal: '#008080',
1650 thistle: '#D8BFD8',
1651 tomato: '#FF6347',
1652 turquoise: '#40E0D0',
1653 violet: '#EE82EE',
1654 wheat: '#F5DEB3',
1655 white: '#FFFFFF',
1656 whitesmoke: '#F5F5F5',
1657 yellow: '#FFFF00',
1658 yellowgreen: '#9ACD32'
1659 },
1660
1661 _borderStyle: [
1662 'none',
1663 'hidden',
1664 'dotted',
1665 'dashed',
1666 'solid',
1667 'double',
1668 'groove',
1669 'ridge',
1670 'inset',
1671 'outset'
1672 ],
1673
1674 _widthRegExp: /^(thin|medium|thick|[\+-]?\d+(\.\d+)?[a-z%]+|[\+-]?0+(\.0+)?|\.\d+[a-z%]+)$/,
1675
1676 _rgbaRegExp: /rgba?\(\s*\d+%?\s*,\s*\d+%?\s*,\s*\d+%?\s*(?:,\s*[0-9.]+\s*)?\)/gi,
1677
1678 _hslaRegExp: /hsla?\(\s*[0-9.]+\s*,\s*\d+%\s*,\s*\d+%\s*(?:,\s*[0-9.]+\s*)?\)/gi,
1679
1680 /**
1681 * Parses the `value` used as a `background` property shorthand and returns information as an object.
1682 *
1683 * **Note:** Currently only the `color` property is extracted. Any other parts will go into the `unprocessed` property.
1684 *
1685 * var background = CKEDITOR.tools.style.parse.background( '#0C0 url(foo.png)' );
1686 * console.log( background );
1687 * // Logs: { color: '#0C0', unprocessed: 'url(foo.png)' }
1688 *
1689 * @param {String} value The value of the `background` property.
1690 * @returns {Object} An object with information extracted from the background.
1691 * @returns {String} return.color The **first** color value found. The color format remains the same as in input.
1692 * @returns {String} return.unprocessed The remaining part of the `value` that has not been processed.
1693 * @member CKEDITOR.tools.style.parse
1694 */
1695 background: function( value ) {
1696 var ret = {},
1697 colors = this._findColor( value );
1698
1699 if ( colors.length ) {
1700 ret.color = colors[ 0 ];
1701
1702 CKEDITOR.tools.array.forEach( colors, function( colorToken ) {
1703 value = value.replace( colorToken, '' );
1704 } );
1705 }
1706
1707 value = CKEDITOR.tools.trim( value );
1708
1709 if ( value ) {
1710 // If anything was left unprocessed include it as unprocessed part.
1711 ret.unprocessed = value;
1712 }
1713
1714 return ret;
1715 },
1716
1717 /**
1718 * Parses the `margin` CSS property shorthand format.
1719 *
1720 * console.log( CKEDITOR.tools.parse.margin( '3px 0 2' ) );
1721 * // Logs: { top: "3px", right: "0", bottom: "2", left: "0" }
1722 *
1723 * @param {String} value The `margin` property value.
1724 * @returns {Object}
1725 * @returns {Number} return.top Top margin.
1726 * @returns {Number} return.right Right margin.
1727 * @returns {Number} return.bottom Bottom margin.
1728 * @returns {Number} return.left Left margin.
1729 * @member CKEDITOR.tools.style.parse
1730 */
1731 margin: function( value ) {
1732 var ret = {};
1733
1734 var widths = value.match( /(?:\-?[\.\d]+(?:%|\w*)|auto|inherit|initial|unset)/g ) || [ '0px' ];
1735
1736 switch ( widths.length ) {
1737 case 1:
1738 mapStyles( [ 0, 0, 0, 0 ] );
1739 break;
1740 case 2:
1741 mapStyles( [ 0, 1, 0, 1 ] );
1742 break;
1743 case 3:
1744 mapStyles( [ 0, 1, 2, 1 ] );
1745 break;
1746 case 4:
1747 mapStyles( [ 0, 1, 2, 3 ] );
1748 break;
1749 }
1750
1751 function mapStyles( map ) {
1752 ret.top = widths[ map[ 0 ] ];
1753 ret.right = widths[ map[ 1 ] ];
1754 ret.bottom = widths[ map[ 2 ] ];
1755 ret.left = widths[ map[ 3 ] ];
1756 }
1757
1758 return ret;
1759 },
1760
1761 /**
1762 * Parses the `border` CSS property shorthand format.
1763 * This CSS property does not support inheritance (https://www.w3.org/TR/css3-background/#the-border-shorthands).
1764 *
1765 * console.log( CKEDITOR.tools.style.parse.border( '3px solid #ffeedd' ) );
1766 * // Logs: { width: "3px", style: "solid", color: "#ffeedd" }
1767 *
1768 * @param {String} value The `border` property value.
1769 * @returns {Object}
1770 * @returns {String} return.width The border-width attribute.
1771 * @returns {String} return.style The border-style attribute.
1772 * @returns {String} return.color The border-color attribute.
1773 * @member CKEDITOR.tools.style.parse
1774 */
1775 border: function( value ) {
1776 var ret = {},
1777 input = value.split( /\s+/ );
1778
1779 CKEDITOR.tools.array.forEach( input, function( val ) {
1780 if ( !ret.color ) {
1781 var parseColor = CKEDITOR.tools.style.parse._findColor( val );
1782 if ( parseColor.length ) {
1783 ret.color = parseColor[ 0 ];
1784 return;
1785 }
1786 }
1787
1788 if ( !ret.style ) {
1789 if ( CKEDITOR.tools.indexOf( CKEDITOR.tools.style.parse._borderStyle, val ) !== -1 ) {
1790 ret.style = val;
1791 return;
1792 }
1793 }
1794
1795 if ( !ret.width ) {
1796 if ( CKEDITOR.tools.style.parse._widthRegExp.test( val ) ) {
1797 ret.width = val;
1798 return;
1799 }
1800 }
1801
1802 } );
1803 return ret;
1804 },
1805
1806 /**
1807 * Searches the `value` for any CSS color occurrences and returns it.
1808 *
1809 * @private
1810 * @param {String} value
1811 * @returns {String[]} An array of matched results.
1812 * @member CKEDITOR.tools.style.parse
1813 */
1814 _findColor: function( value ) {
1815 var ret = [],
1816 arrayTools = CKEDITOR.tools.array;
1817
1818
1819 // Check for rgb(a).
1820 ret = ret.concat( value.match( this._rgbaRegExp ) || [] );
1821
1822 // Check for hsl(a).
1823 ret = ret.concat( value.match( this._hslaRegExp ) || [] );
1824
1825 ret = ret.concat( arrayTools.filter( value.split( /\s+/ ), function( colorEntry ) {
1826 // Check for hex format.
1827 if ( colorEntry.match( /^\#[a-f0-9]{3}(?:[a-f0-9]{3})?$/gi ) ) {
1828 return true;
1829 }
1830
1831 // Check for preset names.
1832 return colorEntry.toLowerCase() in CKEDITOR.tools.style.parse._colors;
1833 } ) );
1834
1835 return ret;
1836 }
1837 }
1838 },
1839
1840 /**
1841 * A set of array helpers.
1842 *
1843 * @property {CKEDITOR.tools.array}
1844 * @member CKEDITOR.tools
1845 */
1846 array: {
1847 /**
1848 * Returns a copy of `array` filtered using the `fn` function. Any elements that the `fn` will return `false` for
1849 * will get removed from the returned array.
1850 *
1851 * var filtered = this.array.filter( [ 0, 1, 2, 3 ], function( value ) {
1852 * // Leave only values equal or greater than 2.
1853 * return value >= 2;
1854 * } );
1855 * console.log( filtered );
1856 * // Logs: [ 2, 3 ]
1857 *
1858 * @param {Array} array
1859 * @param {Function} fn A function that gets called with each `array` item. Any item that `fn`
1860 * returned a `false`-alike value for will be filtered out of the `array`.
1861 * @param {Mixed} fn.value The currently iterated array value.
1862 * @param {Number} fn.index The index of the currently iterated value in an array.
1863 * @param {Array} fn.array The original array passed as the `array` variable.
1864 * @param {Mixed} [thisArg=undefined] A context object for `fn`.
1865 * @returns {Array} The filtered array.
1866 * @member CKEDITOR.tools.array
1867 */
1868 filter: function( array, fn, thisArg ) {
1869 var ret = [];
1870
1871 this.forEach( array, function( val, i ) {
1872 if ( fn.call( thisArg, val, i, array ) ) {
1873 ret.push( val );
1874 }
1875 } );
1876
1877 return ret;
1878 },
1879
1880 /**
1881 * Iterates over every element in the `array`.
1882 *
1883 * @param {Array} array An array to be iterated over.
1884 * @param {Function} fn The function called for every `array` element.
1885 * @param {Mixed} fn.value The currently iterated array value.
1886 * @param {Number} fn.index The index of the currently iterated value in an array.
1887 * @param {Array} fn.array The original array passed as an `array` variable.
1888 * @param {Mixed} [thisArg=undefined] The context object for `fn`.
1889 * @member CKEDITOR.tools.array
1890 */
1891 forEach: function( array, fn, thisArg ) {
1892 var len = array.length,
1893 i;
1894
1895 for ( i = 0; i < len; i++ ) {
1896 fn.call( thisArg, array[ i ], i, array );
1897 }
1898 },
1899
1900 /**
1901 * Applies a function to each element of an array and returns the array of results in the same order.
1902 * Note the order of the parameters.
1903 *
1904 * @param {Array} array An array of elements that `fn` is applied on.
1905 * @param {Function} fn A function with the signature `a -> b`.
1906 * @param {Mixed} [thisArg=undefined] The context object for `fn`.
1907 * @returns {Array} An array of mapped elements.
1908 * @member CKEDITOR.tools.array
1909 * @since 4.6.2
1910 */
1911 map: function( array, fn, thisArg ) {
1912 var result = [];
1913 for ( var i = 0; i < array.length; i++ ) {
1914 result.push( fn.call( thisArg, array[ i ], i, array ) );
1915 }
1916 return result;
1917 },
1918
1919 /**
1920 * Applies a function against each value in an array storing the result in an accumulator passed to the next iteration.
1921 * Note the order of the parameters.
1922 *
1923 * @param {Array} array An array of elements that `fn` is applied on.
1924 * @param {Function} fn A function with the signature `(accumulator, a, index, array) -> b`.
1925 * @param {Mixed} initial Initial value of the accumulator.
1926 * @param {Mixed} [thisArg=undefined] The context object for `fn`.
1927 * @returns {Mixed} The final value of the accumulator.
1928 * @member CKEDITOR.tools.array
1929 * @since 4.6.2
1930 */
1931 reduce: function( array, fn, initial, thisArg ) {
1932 var acc = initial;
1933 for ( var i = 0; i < array.length; i++ ) {
1934 acc = fn.call( thisArg, acc, array[ i ], i, array );
1935 }
1936 return acc;
1937 }
1938 },
1939
1940 /**
1941 * A set of object helpers.
1942 *
1943 * @property {CKEDITOR.tools.object}
1944 * @member CKEDITOR.tools
1945 */
1946 object: {
1947 /**
1948 * Returns the first key from `obj` which has a given `value`.
1949 *
1950 * @param {Object} obj An object whose `key` is looked for.
1951 * @param {Mixed} value An object's `value` to be looked for.
1952 * @returns {String/null} Matched `key` or `null` if not found.
1953 * @member CKEDITOR.tools.object
1954 */
1955
1956 findKey: function( obj, value ) {
1957 if ( typeof obj !== 'object' ) {
1958 return null;
1959 }
1960
1961 var key;
1962
1963 for ( key in obj ) {
1964 if ( obj[ key ] === value ) {
1965 return key;
1966 }
1967 }
1968
1969 return null;
1970 }
1356 } 1971 }
1357 }; 1972 };
1358 1973
@@ -1381,6 +1996,78 @@
1381 1996
1382 return result; 1997 return result;
1383 } 1998 }
1999
2000 /**
2001 * @member CKEDITOR.tools.array
2002 * @method indexOf
2003 * @inheritdoc CKEDITOR.tools#indexOf
2004 */
2005 CKEDITOR.tools.array.indexOf = CKEDITOR.tools.indexOf;
2006
2007 /**
2008 * @member CKEDITOR.tools.array
2009 * @method isArray
2010 * @inheritdoc CKEDITOR.tools#isArray
2011 */
2012 CKEDITOR.tools.array.isArray = CKEDITOR.tools.isArray;
2013
2014 /**
2015 * Left mouse button.
2016 *
2017 * @since 4.7.3
2018 * @readonly
2019 * @property {Number} [=0]
2020 * @member CKEDITOR
2021 */
2022 CKEDITOR.MOUSE_BUTTON_LEFT = 0;
2023
2024 /**
2025 * Middle mouse button.
2026 *
2027 * @since 4.7.3
2028 * @readonly
2029 * @property {Number} [=1]
2030 * @member CKEDITOR
2031 */
2032 CKEDITOR.MOUSE_BUTTON_MIDDLE = 1;
2033
2034 /**
2035 * Right mouse button.
2036 *
2037 * @since 4.7.3
2038 * @readonly
2039 * @property {Number} [=2]
2040 * @member CKEDITOR
2041 */
2042 CKEDITOR.MOUSE_BUTTON_RIGHT = 2;
2043
2044 /**
2045 * The namespace containing functions to work on CSS properties.
2046 *
2047 * @since 4.6.1
2048 * @class CKEDITOR.tools.style
2049 */
2050
2051 /**
2052 * The namespace with helper functions to parse some common CSS properties.
2053 *
2054 * @since 4.6.1
2055 * @class CKEDITOR.tools.style.parse
2056 */
2057
2058 /**
2059 * The namespace with helper functions and polyfills for arrays.
2060 *
2061 * @since 4.6.1
2062 * @class CKEDITOR.tools.array
2063 */
2064
2065 /**
2066 * The namespace with helper functions and polyfills for objects.
2067 *
2068 * @since 4.7.1
2069 * @class CKEDITOR.tools.object
2070 */
1384} )(); 2071} )();
1385 2072
1386// PACKAGER_RENAME( CKEDITOR.tools ) 2073// PACKAGER_RENAME( CKEDITOR.tools )
diff --git a/sources/core/ui.js b/sources/core/ui.js
index 29ab0ad..ac5a285 100644
--- a/sources/core/ui.js
+++ b/sources/core/ui.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