      1 ﻿/*
      2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
      3 For licensing, see LICENSE.html or http://ckeditor.com/license
      4 */
      5
      6 (function()
      7 {
      8 	// #### checkSelectionChange : START
      9
     10 	// The selection change check basically saves the element parent tree of
     11 	// the current node and check it on successive requests. If there is any
     12 	// change on the tree, then the selectionChange event gets fired.
     13 	function checkSelectionChange()
     14 	{
     15 		try
     16 		{
     17 			// In IE, the "selectionchange" event may still get thrown when
     18 			// releasing the WYSIWYG mode, so we need to check it first.
     19 			var sel = this.getSelection();
     20 			if ( !sel )
     21 				return;
     22
     23 			var firstElement = sel.getStartElement();
     24 			var currentPath = new CKEDITOR.dom.elementPath( firstElement );
     25
     26 			if ( !currentPath.compare( this._.selectionPreviousPath ) )
     27 			{
     28 				this._.selectionPreviousPath = currentPath;
     29 				this.fire( 'selectionChange', { selection : sel, path : currentPath, element : firstElement } );
     30 			}
     31 		}
     32 		catch (e)
     33 		{}
     34 	}
     35
     36 	var checkSelectionChangeTimer,
     37 		checkSelectionChangeTimeoutPending;
     38
     39 	function checkSelectionChangeTimeout()
     40 	{
     41 		// Firing the "OnSelectionChange" event on every key press started to
     42 		// be too slow. This function guarantees that there will be at least
     43 		// 200ms delay between selection checks.
     44
     45 		checkSelectionChangeTimeoutPending = true;
     46
     47 		if ( checkSelectionChangeTimer )
     48 			return;
     49
     50 		checkSelectionChangeTimeoutExec.call( this );
     51
     52 		checkSelectionChangeTimer = CKEDITOR.tools.setTimeout( checkSelectionChangeTimeoutExec, 200, this );
     53 	}
     54
     55 	function checkSelectionChangeTimeoutExec()
     56 	{
     57 		checkSelectionChangeTimer = null;
     58
     59 		if ( checkSelectionChangeTimeoutPending )
     60 		{
     61 			// Call this with a timeout so the browser properly moves the
     62 			// selection after the mouseup. It happened that the selection was
     63 			// being moved after the mouseup when clicking inside selected text
     64 			// with Firefox.
     65 			CKEDITOR.tools.setTimeout( checkSelectionChange, 0, this );
     66
     67 			checkSelectionChangeTimeoutPending = false;
     68 		}
     69 	}
     70
     71 	// #### checkSelectionChange : END
     72
     73 	var selectAllCmd =
     74 	{
     75 		exec : function( editor )
     76 		{
     77 			switch ( editor.mode )
     78 			{
     79 				case 'wysiwyg' :
     80 					editor.document.$.execCommand( 'SelectAll', false, null );
     81 					break;
     82 				case 'source' :
     83 					// TODO
     84 			}
     85 		},
     86 		canUndo : false
     87 	};
     88
     89 	CKEDITOR.plugins.add( 'selection',
     90 	{
     91 		init : function( editor )
     92 		{
     93 			editor.on( 'contentDom', function()
     94 				{
     95 					var doc = editor.document;
     96
     97 					if ( CKEDITOR.env.ie )
     98 					{
     99 						// Other browsers don't loose the selection if the
    100 						// editor document loose the focus. In IE, we don't
    101 						// have support for it, so we reproduce it here, other
    102 						// than firing the selection change event.
    103
    104 						var savedRange,
    105 							saveEnabled;
    106
    107 						// "onfocusin" is fired before "onfocus". It makes it
    108 						// possible to restore the selection before click
    109 						// events get executed.
    110 						doc.on( 'focusin', function()
    111 							{
    112 								// If we have saved a range, restore it at this
    113 								// point.
    114 								if ( savedRange )
    115 								{
    116 									// Well not break because of this.
    117 									try
    118 									{
    119 										savedRange.select();
    120 									}
    121 									catch (e)
    122 									{}
    123
    124 									savedRange = null;
    125 								}
    126 							});
    127
    128 						editor.window.on( 'focus', function()
    129 							{
    130 								// Enable selections to be saved.
    131 								saveEnabled = true;
    132
    133 								saveSelection();
    134 							});
    135
    136 						editor.window.on( 'blur', function()
    137 							{
    138 								// Disable selections from being saved.
    139 								saveEnabled = false;
    140
    141 								// IE may leave the selection still inside the
    142 								// document. Let's force it to be removed.
    143 								// TODO: The following has effect for
    144 								// collapsed selections.
    145 								editor.document.$.execCommand( 'Unselect' );
    146 							});
    147
    148 						// IE fires the "selectionchange" event when clicking
    149 						// inside a selection. We don't want to capture that.
    150 						doc.on( 'mousedown', disableSave );
    151 						doc.on( 'mouseup',
    152 							function()
    153 							{
    154 								saveEnabled = true;
    155 								setTimeout( function()
    156 									{
    157 										saveSelection( true );
    158 									},
    159 									0 );
    160 							});
    161
    162 						doc.on( 'keydown', disableSave );
    163 						doc.on( 'keyup',
    164 							function()
    165 							{
    166 								saveEnabled = true;
    167 								saveSelection();
    168 							});
    169
    170
    171 						// IE is the only to provide the "selectionchange"
    172 						// event.
    173 						doc.on( 'selectionchange', saveSelection );
    174
    175 						function disableSave()
    176 						{
    177 							saveEnabled = false;
    178 						}
    179
    180 						function saveSelection( testIt )
    181 						{
    182 							if ( saveEnabled )
    183 							{
    184 								var doc = editor.document,
    185 									sel = doc && doc.$.selection;
    186
    187 								// There is a very specific case, when clicking
    188 								// inside a text selection. In that case, the
    189 								// selection collapses at the clicking point,
    190 								// but the selection object remains in an
    191 								// unknown state, making createRange return a
    192 								// range at the very start of the document. In
    193 								// such situation we have to test the range, to
    194 								// be sure it's valid.
    195 								if ( testIt && sel && sel.type == 'None' )
    196 								{
    197 									// The "InsertImage" command can be used to
    198 									// test whether the selection is good or not.
    199 									// If not, it's enough to give some time to
    200 									// IE to put things in order for us.
    201 									if ( !doc.$.queryCommandEnabled( 'InsertImage' ) )
    202 									{
    203 										CKEDITOR.tools.setTimeout( saveSelection, 50, this, true );
    204 										return;
    205 									}
    206 								}
    207
    208 								savedRange = sel && sel.createRange();
    209
    210 								checkSelectionChangeTimeout.call( editor );
    211 							}
    212 						}
    213 					}
    214 					else
    215 					{
    216 						// In other browsers, we make the selection change
    217 						// check based on other events, like clicks or keys
    218 						// press.
    219
    220 						doc.on( 'mouseup', checkSelectionChangeTimeout, editor );
    221 						doc.on( 'keyup', checkSelectionChangeTimeout, editor );
    222 					}
    223 				});
    224
    225 			editor.addCommand( 'selectAll', selectAllCmd );
    226 			editor.ui.addButton( 'SelectAll',
    227 				{
    228 					label : editor.lang.selectAll,
    229 					command : 'selectAll'
    230 				});
    231
    232 			editor.selectionChange = checkSelectionChangeTimeout;
    233 		}
    234 	});
    235
    236 	/**
    237 	 * Gets the current selection from the editing area when in WYSIWYG mode.
    238 	 * @returns {CKEDITOR.dom.selection} A selection object or null if not on
    239 	 *		WYSIWYG mode or no selection is available.
    240 	 * @example
    241 	 * var selection = CKEDITOR.instances.editor1.<b>getSelection()</b>;
    242 	 * alert( selection.getType() );
    243 	 */
    244 	CKEDITOR.editor.prototype.getSelection = function()
    245 	{
    246 		return this.document && this.document.getSelection();
    247 	};
    248
    249 	CKEDITOR.editor.prototype.forceNextSelectionCheck = function()
    250 	{
    251 		delete this._.selectionPreviousPath;
    252 	};
    253
    254 	/**
    255 	 * Gets the current selection from the document.
    256 	 * @returns {CKEDITOR.dom.selection} A selection object.
    257 	 * @example
    258 	 * var selection = CKEDITOR.instances.editor1.document.<b>getSelection()</b>;
    259 	 * alert( selection.getType() );
    260 	 */
    261 	CKEDITOR.dom.document.prototype.getSelection = function()
    262 	{
    263 		var sel = new CKEDITOR.dom.selection( this );
    264 		return ( !sel || sel.isInvalid ) ? null : sel;
    265 	};
    266
    267 	/**
    268 	 * No selection.
    269 	 * @constant
    270 	 * @example
    271 	 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_NONE )
    272 	 *     alert( 'Nothing is selected' );
    273 	 */
    274 	CKEDITOR.SELECTION_NONE		= 1;
    275
    276 	/**
    277 	 * Text or collapsed selection.
    278 	 * @constant
    279 	 * @example
    280 	 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_TEXT )
    281 	 *     alert( 'Text is selected' );
    282 	 */
    283 	CKEDITOR.SELECTION_TEXT		= 2;
    284
    285 	/**
    286 	 * Element selection.
    287 	 * @constant
    288 	 * @example
    289 	 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_ELEMENT )
    290 	 *     alert( 'An element is selected' );
    291 	 */
    292 	CKEDITOR.SELECTION_ELEMENT	= 3;
    293
    294 	/**
    295 	 * Manipulates the selection in a DOM document.
    296 	 * @constructor
    297 	 * @example
    298 	 */
    299 	CKEDITOR.dom.selection = function( document )
    300 	{
    301 		var lockedSelection = document.getCustomData( 'cke_locked_selection' );
    302
    303 		if ( lockedSelection )
    304 			return lockedSelection;
    305
    306 		this.document = document;
    307 		this.isLocked = false;
    308 		this._ =
    309 		{
    310 			cache : {}
    311 		};
    312
    313 		/**
    314 		 * IE BUG: The selection's document may be a different document than the
    315 		 * editor document. Return null if that's the case.
    316 		 */
    317 		if ( CKEDITOR.env.ie )
    318 		{
    319 			var range = this.getNative().createRange();
    320 			if ( !range
    321 				|| ( range.item && range.item(0).ownerDocument != this.document.$ )
    322 				|| ( range.parentElement && range.parentElement().ownerDocument != this.document.$ ) )
    323 			{
    324 				this.isInvalid = true;
    325 			}
    326 		}
    327
    328 		return this;
    329 	};
    330
    331 	var styleObjectElements =
    332 	{
    333 		img:1,hr:1,li:1,table:1,tr:1,td:1,embed:1,object:1,ol:1,ul:1,
    334 		a:1, input:1, form:1, select:1, textarea:1, button:1, fieldset:1, th:1, thead:1, tfoot:1
    335 	};
    336
    337 	CKEDITOR.dom.selection.prototype =
    338 	{
    339 		/**
    340 		 * Gets the native selection object from the browser.
    341 		 * @function
    342 		 * @returns {Object} The native selection object.
    343 		 * @example
    344 		 * var selection = editor.getSelection().<b>getNative()</b>;
    345 		 */
    346 		getNative :
    347 			CKEDITOR.env.ie ?
    348 				function()
    349 				{
    350 					return this._.cache.nativeSel || ( this._.cache.nativeSel = this.document.$.selection );
    351 				}
    352 			:
    353 				function()
    354 				{
    355 					return this._.cache.nativeSel || ( this._.cache.nativeSel = this.document.getWindow().$.getSelection() );
    356 				},
    357
    358 		/**
    359 		 * Gets the type of the current selection. The following values are
    360 		 * available:
    361 		 * <ul>
    362 		 *		<li>{@link CKEDITOR.SELECTION_NONE} (1): No selection.</li>
    363 		 *		<li>{@link CKEDITOR.SELECTION_TEXT} (2): Text is selected or
    364 		 *			collapsed selection.</li>
    365 		 *		<li>{@link CKEDITOR.SELECTION_ELEMENT} (3): A element
    366 		 *			selection.</li>
    367 		 * </ul>
    368 		 * @function
    369 		 * @returns {Number} One of the following constant values:
    370 		 *		{@link CKEDITOR.SELECTION_NONE}, {@link CKEDITOR.SELECTION_TEXT} or
    371 		 *		{@link CKEDITOR.SELECTION_ELEMENT}.
    372 		 * @example
    373 		 * if ( editor.getSelection().<b>getType()</b> == CKEDITOR.SELECTION_TEXT )
    374 		 *     alert( 'Text is selected' );
    375 		 */
    376 		getType :
    377 			CKEDITOR.env.ie ?
    378 				function()
    379 				{
    380 					var cache = this._.cache;
    381 					if ( cache.type )
    382 						return cache.type;
    383
    384 					var type = CKEDITOR.SELECTION_NONE;
    385
    386 					try
    387 					{
    388 						var sel = this.getNative(),
    389 							ieType = sel.type;
    390
    391 						if ( ieType == 'Text' )
    392 							type = CKEDITOR.SELECTION_TEXT;
    393
    394 						if ( ieType == 'Control' )
    395 							type = CKEDITOR.SELECTION_ELEMENT;
    396
    397 						// It is possible that we can still get a text range
    398 						// object even when type == 'None' is returned by IE.
    399 						// So we'd better check the object returned by
    400 						// createRange() rather than by looking at the type.
    401 						if ( sel.createRange().parentElement )
    402 							type = CKEDITOR.SELECTION_TEXT;
    403 					}
    404 					catch(e) {}
    405
    406 					return ( cache.type = type );
    407 				}
    408 			:
    409 				function()
    410 				{
    411 					var cache = this._.cache;
    412 					if ( cache.type )
    413 						return cache.type;
    414
    415 					var type = CKEDITOR.SELECTION_TEXT;
    416
    417 					var sel = this.getNative();
    418
    419 					if ( !sel )
    420 						type = CKEDITOR.SELECTION_NONE;
    421 					else if ( sel.rangeCount == 1 )
    422 					{
    423 						// Check if the actual selection is a control (IMG,
    424 						// TABLE, HR, etc...).
    425
    426 						var range = sel.getRangeAt(0),
    427 							startContainer = range.startContainer;
    428
    429 						if ( startContainer == range.endContainer
    430 							&& startContainer.nodeType == 1
    431 							&& ( range.endOffset - range.startOffset ) == 1
    432 							&& styleObjectElements[ startContainer.childNodes[ range.startOffset ].nodeName.toLowerCase() ] )
    433 						{
    434 							type = CKEDITOR.SELECTION_ELEMENT;
    435 						}
    436 					}
    437
    438 					return ( cache.type = type );
    439 				},
    440
    441 		getRanges :
    442 			CKEDITOR.env.ie ?
    443 				( function()
    444 				{
    445 					// Finds the container and offset for a specific boundary
    446 					// of an IE range.
    447 					var getBoundaryInformation = function( range, start )
    448 					{
    449 						// Creates a collapsed range at the requested boundary.
    450 						range = range.duplicate();
    451 						range.collapse( start );
    452
    453 						// Gets the element that encloses the range entirely.
    454 						var parent = range.parentElement();
    455 						var siblings = parent.childNodes;
    456
    457 						var testRange;
    458
    459 						for ( var i = 0 ; i < siblings.length ; i++ )
    460 						{
    461 							var child = siblings[ i ];
    462 							if ( child.nodeType == 1 )
    463 							{
    464 								testRange = range.duplicate();
    465
    466 								testRange.moveToElementText( child );
    467 								testRange.collapse();
    468
    469 								var comparison = testRange.compareEndPoints( 'StartToStart', range );
    470
    471 								if ( comparison > 0 )
    472 									break;
    473 								else if ( comparison === 0 )
    474 									return {
    475 										container : parent,
    476 										offset : i
    477 									};
    478
    479 								testRange = null;
    480 							}
    481 						}
    482
    483 						if ( !testRange )
    484 						{
    485 							testRange = range.duplicate();
    486 							testRange.moveToElementText( parent );
    487 							testRange.collapse( false );
    488 						}
    489
    490 						testRange.setEndPoint( 'StartToStart', range );
    491 						var distance = testRange.text.length;
    492
    493 						while ( distance > 0 )
    494 							distance -= siblings[ --i ].nodeValue.length;
    495
    496 						if ( distance === 0 )
    497 						{
    498 							return {
    499 								container : parent,
    500 								offset : i
    501 							};
    502 						}
    503 						else
    504 						{
    505 							return {
    506 								container : siblings[ i ],
    507 								offset : -distance
    508 							};
    509 						}
    510 					};
    511
    512 					return function()
    513 					{
    514 						var cache = this._.cache;
    515 						if ( cache.ranges )
    516 							return cache.ranges;
    517
    518 						// IE doesn't have range support (in the W3C way), so we
    519 						// need to do some magic to transform selections into
    520 						// CKEDITOR.dom.range instances.
    521
    522 						var sel = this.getNative(),
    523 							nativeRange = sel && sel.createRange(),
    524 							type = this.getType(),
    525 							range;
    526
    527 						if ( !sel )
    528 							return [];
    529
    530 						if ( type == CKEDITOR.SELECTION_TEXT )
    531 						{
    532 							range = new CKEDITOR.dom.range( this.document );
    533
    534 							var boundaryInfo = getBoundaryInformation( nativeRange, true );
    535 							range.setStart( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset );
    536
    537 							boundaryInfo = getBoundaryInformation( nativeRange );
    538 							range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset );
    539
    540 							return ( cache.ranges = [ range ] );
    541 						}
    542 						else if ( type == CKEDITOR.SELECTION_ELEMENT )
    543 						{
    544 							var retval = this._.cache.ranges = [];
    545
    546 							for ( var i = 0 ; i < nativeRange.length ; i++ )
    547 							{
    548 								var element = nativeRange.item( i ),
    549 									parentElement = element.parentNode,
    550 									j = 0;
    551
    552 								range = new CKEDITOR.dom.range( this.document );
    553
    554 								for (; j < parentElement.childNodes.length && parentElement.childNodes[j] != element ; j++ )
    555 								{ /*jsl:pass*/ }
    556
    557 								range.setStart( new CKEDITOR.dom.node( parentElement ), j );
    558 								range.setEnd( new CKEDITOR.dom.node( parentElement ), j + 1 );
    559 								retval.push( range );
    560 							}
    561
    562 							return retval;
    563 						}
    564
    565 						return ( cache.ranges = [] );
    566 					};
    567 				})()
    568 			:
    569 				function()
    570 				{
    571 					var cache = this._.cache;
    572 					if ( cache.ranges )
    573 						return cache.ranges;
    574
    575 					// On browsers implementing the W3C range, we simply
    576 					// tranform the native ranges in CKEDITOR.dom.range
    577 					// instances.
    578
    579 					var ranges = [];
    580 					var sel = this.getNative();
    581
    582 					if ( !sel )
    583 						return [];
    584
    585 					for ( var i = 0 ; i < sel.rangeCount ; i++ )
    586 					{
    587 						var nativeRange = sel.getRangeAt( i );
    588 						var range = new CKEDITOR.dom.range( this.document );
    589
    590 						range.setStart( new CKEDITOR.dom.node( nativeRange.startContainer ), nativeRange.startOffset );
    591 						range.setEnd( new CKEDITOR.dom.node( nativeRange.endContainer ), nativeRange.endOffset );
    592 						ranges.push( range );
    593 					}
    594
    595 					return ( cache.ranges = ranges );
    596 				},
    597
    598 		/**
    599 		 * Gets the DOM element in which the selection starts.
    600 		 * @returns {CKEDITOR.dom.element} The element at the beginning of the
    601 		 *		selection.
    602 		 * @example
    603 		 * var element = editor.getSelection().<b>getStartElement()</b>;
    604 		 * alert( element.getName() );
    605 		 */
    606 		getStartElement : function()
    607 		{
    608 			var cache = this._.cache;
    609 			if ( cache.startElement !== undefined )
    610 				return cache.startElement;
    611
    612 			var node,
    613 				sel = this.getNative();
    614
    615 			switch ( this.getType() )
    616 			{
    617 				case CKEDITOR.SELECTION_ELEMENT :
    618 					return this.getSelectedElement();
    619
    620 				case CKEDITOR.SELECTION_TEXT :
    621
    622 					var range = this.getRanges()[0];
    623
    624 					if ( range )
    625 					{
    626 						if ( !range.collapsed )
    627 						{
    628 							range.optimize();
    629
    630 							// Decrease the range content to exclude particial
    631 							// selected node on the start which doesn't have
    632 							// visual impact. ( #3231 )
    633 							while( true )
    634 							{
    635 								var startContainer = range.startContainer,
    636 									startOffset = range.startOffset;
    637 								if ( startOffset == ( startContainer.getChildCount ?
    638 									startContainer.getChildCount() : startContainer.getLength() ) )
    639 									range.setStartAfter( startContainer );
    640 								else break;
    641 							}
    642
    643 							node = range.startContainer;
    644
    645 							if ( node.type != CKEDITOR.NODE_ELEMENT )
    646 								return node.getParent();
    647
    648 							node = node.getChild( range.startOffset );
    649
    650 							if ( !node || node.type != CKEDITOR.NODE_ELEMENT )
    651 								return range.startContainer;
    652
    653 							var child = node.getFirst();
    654 							while (  child && child.type == CKEDITOR.NODE_ELEMENT )
    655 							{
    656 								node = child;
    657 								child = child.getFirst();
    658 							}
    659
    660 							return node;
    661 						}
    662 					}
    663
    664 					if ( CKEDITOR.env.ie )
    665 					{
    666 						range = sel.createRange();
    667 						range.collapse( true );
    668
    669 						node = range.parentElement();
    670 					}
    671 					else
    672 					{
    673 						node = sel.anchorNode;
    674
    675 						if ( node.nodeType != 1 )
    676 							node = node.parentNode;
    677 					}
    678 			}
    679
    680 			return cache.startElement = ( node ? new CKEDITOR.dom.element( node ) : null );
    681 		},
    682
    683 		/**
    684 		 * Gets the current selected element.
    685 		 * @returns {CKEDITOR.dom.element} The selected element. Null if no
    686 		 *		selection is available or the selection type is not
    687 		 *		{@link CKEDITOR.SELECTION_ELEMENT}.
    688 		 * @example
    689 		 * var element = editor.getSelection().<b>getSelectedElement()</b>;
    690 		 * alert( element.getName() );
    691 		 */
    692 		getSelectedElement : function()
    693 		{
    694 			var cache = this._.cache;
    695 			if ( cache.selectedElement !== undefined )
    696 				return cache.selectedElement;
    697
    698 			var node;
    699
    700 			if ( this.getType() == CKEDITOR.SELECTION_ELEMENT )
    701 			{
    702 				var sel = this.getNative();
    703
    704 				if ( CKEDITOR.env.ie )
    705 				{
    706 					try
    707 					{
    708 						node = sel.createRange().item(0);
    709 					}
    710 					catch(e) {}
    711 				}
    712 				else
    713 				{
    714 					var range = sel.getRangeAt( 0 );
    715 					node = range.startContainer.childNodes[ range.startOffset ];
    716 				}
    717 			}
    718
    719 			return cache.selectedElement = ( node ? new CKEDITOR.dom.element( node ) : null );
    720 		},
    721
    722 		lock : function()
    723 		{
    724 			// Call all cacheable function.
    725 			this.getRanges();
    726 			this.getStartElement();
    727 			this.getSelectedElement();
    728
    729 			// The native selection is not available when locked.
    730 			this._.cache.nativeSel = {};
    731
    732 			this.isLocked = true;
    733
    734 			// Save this selection inside the DOM document.
    735 			this.document.setCustomData( 'cke_locked_selection', this );
    736 		},
    737
    738 		unlock : function( restore )
    739 		{
    740 			var doc = this.document,
    741 				lockedSelection = doc.getCustomData( 'cke_locked_selection' );
    742
    743 			if ( lockedSelection )
    744 			{
    745 				doc.setCustomData( 'cke_locked_selection', null );
    746
    747 				if ( restore )
    748 				{
    749 					var selectedElement = lockedSelection.getSelectedElement(),
    750 						ranges = !selectedElement && lockedSelection.getRanges();
    751
    752 					this.isLocked = false;
    753 					this.reset();
    754
    755 					doc.getBody().focus();
    756
    757 					if ( selectedElement )
    758 						this.selectElement( selectedElement );
    759 					else
    760 						this.selectRanges( ranges );
    761 				}
    762 			}
    763
    764 			if  ( !lockedSelection || !restore )
    765 			{
    766 				this.isLocked = false;
    767 				this.reset();
    768 			}
    769 		},
    770
    771 		reset : function()
    772 		{
    773 			this._.cache = {};
    774 		},
    775
    776 		selectElement : function( element )
    777 		{
    778 			if ( this.isLocked )
    779 			{
    780 				var range = new CKEDITOR.dom.range();
    781 				range.setStartBefore( element );
    782 				range.setEndAfter( element );
    783
    784 				this._.cache.selectedElement = element;
    785 				this._.cache.startElement = element;
    786 				this._.cache.ranges = [ range ];
    787 				this._.cache.type = CKEDITOR.SELECTION_ELEMENT;
    788
    789 				return;
    790 			}
    791
    792 			if ( CKEDITOR.env.ie )
    793 			{
    794 				this.getNative().empty();
    795
    796 				try
    797 				{
    798 					// Try to select the node as a control.
    799 					range = this.document.$.body.createControlRange();
    800 					range.addElement( element.$ );
    801 					range.select();
    802 				}
    803 				catch(e)
    804 				{
    805 					// If failed, select it as a text range.
    806 					range = this.document.$.body.createTextRange();
    807 					range.moveToElementText( element.$ );
    808 					range.select();
    809 				}
    810
    811 				this.reset();
    812 			}
    813 			else
    814 			{
    815 				// Create the range for the element.
    816 				range = this.document.$.createRange();
    817 				range.selectNode( element.$ );
    818
    819 				// Select the range.
    820 				var sel = this.getNative();
    821 				sel.removeAllRanges();
    822 				sel.addRange( range );
    823
    824 				this.reset();
    825 			}
    826 		},
    827
    828 		selectRanges : function( ranges )
    829 		{
    830 			if ( this.isLocked )
    831 			{
    832 				this._.cache.selectedElement = null;
    833 				this._.cache.startElement = ranges[ 0 ].getTouchedStartNode();
    834 				this._.cache.ranges = ranges;
    835 				this._.cache.type = CKEDITOR.SELECTION_TEXT;
    836
    837 				return;
    838 			}
    839
    840 			if ( CKEDITOR.env.ie )
    841 			{
    842 				// IE doesn't accept multiple ranges selection, so we just
    843 				// select the first one.
    844 				if ( ranges[ 0 ] )
    845 					ranges[ 0 ].select();
    846
    847 				this.reset();
    848 			}
    849 			else
    850 			{
    851 				var sel = this.getNative();
    852 				sel.removeAllRanges();
    853
    854 				for ( var i = 0 ; i < ranges.length ; i++ )
    855 				{
    856 					var range = ranges[ i ];
    857 					var nativeRange = this.document.$.createRange();
    858 					nativeRange.setStart( range.startContainer.$, range.startOffset );
    859 					nativeRange.setEnd( range.endContainer.$, range.endOffset );
    860
    861 					// Select the range.
    862 					sel.addRange( nativeRange );
    863 				}
    864
    865 				this.reset();
    866 			}
    867 		},
    868
    869 		createBookmarks : function( serializable )
    870 		{
    871 			var retval = [],
    872 				ranges = this.getRanges(),
    873 				length = ranges.length,
    874 				bookmark;
    875 			for ( var i = 0; i < length ; i++ )
    876 			{
    877 			    retval.push( bookmark = ranges[ i ].createBookmark( serializable, true ) );
    878
    879 				serializable = bookmark.serializable;
    880
    881 				var bookmarkStart = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode,
    882 					bookmarkEnd = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode;
    883
    884 			    // Updating the offset values for rest of ranges which have been mangled(#3256).
    885 			    for ( var j = i + 1 ; j < length ; j++ )
    886 			    {
    887 			        var dirtyRange = ranges[ j ],
    888 			               rangeStart = dirtyRange.startContainer,
    889 			               rangeEnd = dirtyRange.endContainer;
    890
    891 			       rangeStart.equals( bookmarkStart.getParent() ) && dirtyRange.startOffset++;
    892 			       rangeStart.equals( bookmarkEnd.getParent() ) && dirtyRange.startOffset++;
    893 			       rangeEnd.equals( bookmarkStart.getParent() ) && dirtyRange.endOffset++;
    894 			       rangeEnd.equals( bookmarkEnd.getParent() ) && dirtyRange.endOffset++;
    895 			    }
    896 			}
    897
    898 			return retval;
    899 		},
    900
    901 		createBookmarks2 : function( normalized )
    902 		{
    903 			var bookmarks = [],
    904 				ranges = this.getRanges();
    905
    906 			for ( var i = 0 ; i < ranges.length ; i++ )
    907 				bookmarks.push( ranges[i].createBookmark2( normalized ) );
    908
    909 			return bookmarks;
    910 		},
    911
    912 		selectBookmarks : function( bookmarks )
    913 		{
    914 			var ranges = [];
    915 			for ( var i = 0 ; i < bookmarks.length ; i++ )
    916 			{
    917 				var range = new CKEDITOR.dom.range( this.document );
    918 				range.moveToBookmark( bookmarks[i] );
    919 				ranges.push( range );
    920 			}
    921 			this.selectRanges( ranges );
    922 			return this;
    923 		}
    924 	};
    925 })();
    926
    927 CKEDITOR.dom.range.prototype.select =
    928 	CKEDITOR.env.ie ?
    929 		// V2
    930 		function()
    931 		{
    932 			var collapsed = this.collapsed;
    933 			var isStartMakerAlone;
    934 			var dummySpan;
    935
    936 			var bookmark = this.createBookmark();
    937
    938 			// Create marker tags for the start and end boundaries.
    939 			var startNode = bookmark.startNode;
    940
    941 			var endNode;
    942 			if ( !collapsed )
    943 				endNode = bookmark.endNode;
    944
    945 			// Create the main range which will be used for the selection.
    946 			var ieRange = this.document.$.body.createTextRange();
    947
    948 			// Position the range at the start boundary.
    949 			ieRange.moveToElementText( startNode.$ );
    950 			ieRange.moveStart( 'character', 1 );
    951
    952 			if ( endNode )
    953 			{
    954 				// Create a tool range for the end.
    955 				var ieRangeEnd = this.document.$.body.createTextRange();
    956
    957 				// Position the tool range at the end.
    958 				ieRangeEnd.moveToElementText( endNode.$ );
    959
    960 				// Move the end boundary of the main range to match the tool range.
    961 				ieRange.setEndPoint( 'EndToEnd', ieRangeEnd );
    962 				ieRange.moveEnd( 'character', -1 );
    963 			}
    964 			else
    965 			{
    966 // The isStartMakerAlone logic comes from V2. It guarantees that the lines
    967 // will expand and that the cursor will be blinking on the right place.
    968 // Actually, we are using this flag just to avoid using this hack in all
    969 // situations, but just on those needed.
    970
    971 // But, in V3, somehow it is not interested on working whe hitting SHIFT+ENTER
    972 // inside text. So, let's jsut leave the hack happen always.
    973
    974 // I'm still leaving the code here just in case. We may find some other IE
    975 // weirdness and uncommenting this stuff may be useful.
    976
    977 //				isStartMakerAlone = ( !startNode.hasPrevious() || ( startNode.getPrevious().is && startNode.getPrevious().is( 'br' ) ) )
    978 //					&& !startNode.hasNext();
    979
    980 				// Append a temporary <span>﻿</span> before the selection.
    981 				// This is needed to avoid IE destroying selections inside empty
    982 				// inline elements, like <b></b> (#253).
    983 				// It is also needed when placing the selection right after an inline
    984 				// element to avoid the selection moving inside of it.
    985 				dummySpan = this.document.createElement( 'span' );
    986 				dummySpan.setHtml( '﻿' );	// Zero Width No-Break Space (U+FEFF). See #1359.
    987 				dummySpan.insertBefore( startNode );
    988
    989 //				if ( isStartMakerAlone )
    990 //				{
    991 					// To expand empty blocks or line spaces after <br>, we need
    992 					// instead to have any char, which will be later deleted using the
    993 					// selection.
    994 					// \ufeff = Zero Width No-Break Space (U+FEFF). (#1359)
    995 					this.document.createText( '\ufeff' ).insertBefore( startNode );
    996 //				}
    997 			}
    998
    999 			// Remove the markers (reset the position, because of the changes in the DOM tree).
    1000 			this.setStartBefore( startNode );
    1001 			startNode.remove();
    1002
    1003 			if ( collapsed )
    1004 			{
    1005 //				if ( isStartMakerAlone )
    1006 //				{
    1007 					// Move the selection start to include the temporary \ufeff.
    1008 					ieRange.moveStart( 'character', -1 );
    1009
    1010 					ieRange.select();
    1011
    1012 					// Remove our temporary stuff.
    1013 					this.document.$.selection.clear();
    1014 //				}
    1015 //				else
    1016 //					ieRange.select();
    1017
    1018 				dummySpan.remove();
    1019 			}
    1020 			else
    1021 			{
    1022 				this.setEndBefore( endNode );
    1023 				endNode.remove();
    1024 				ieRange.select();
    1025 			}
    1026 		}
    1027 	:
    1028 		function()
    1029 		{
    1030 			var startContainer = this.startContainer;
    1031
    1032 			// If we have a collapsed range, inside an empty element, we must add
    1033 			// something to it, otherwise the caret will not be visible.
    1034 			if ( this.collapsed && startContainer.type == CKEDITOR.NODE_ELEMENT && !startContainer.getChildCount() )
    1035 				startContainer.append( new CKEDITOR.dom.text( '' ) );
    1036
    1037 			var nativeRange = this.document.$.createRange();
    1038 			nativeRange.setStart( startContainer.$, this.startOffset );
    1039
    1040 			try
    1041 			{
    1042 				nativeRange.setEnd( this.endContainer.$, this.endOffset );
    1043 			}
    1044 			catch ( e )
    1045 			{
    1046 				// There is a bug in Firefox implementation (it would be too easy
    1047 				// otherwise). The new start can't be after the end (W3C says it can).
    1048 				// So, let's create a new range and collapse it to the desired point.
    1049 				if ( e.toString().indexOf( 'NS_ERROR_ILLEGAL_VALUE' ) >= 0 )
    1050 				{
    1051 					this.collapse( true );
    1052 					nativeRange.setEnd( this.endContainer.$, this.endOffset );
    1053 				}
    1054 				else
    1055 					throw( e );
    1056 			}
    1057
    1058 			var selection = this.document.getSelection().getNative();
    1059 			selection.removeAllRanges();
    1060 			selection.addRange( nativeRange );
    1061 		};
    1062