1 /*
  2 Copyright (c) 2003-2012, 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 || !sel.document.getWindow().$ )
 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 	function rangeRequiresFix( range )
 74 	{
 75 		function isTextCt( node, isAtEnd )
 76 		{
 77 			if ( !node || node.type == CKEDITOR.NODE_TEXT )
 78 				return false;
 79 
 80 			var testRng = range.clone();
 81 			return testRng[ 'moveToElementEdit' + ( isAtEnd ? 'End' : 'Start' ) ]( node );
 82 		}
 83 
 84 		var ct = range.startContainer;
 85 
 86 		var previous = range.getPreviousNode( isVisible, null, ct ),
 87 			next = range.getNextNode( isVisible, null, ct );
 88 
 89 		// Any adjacent text container may absorb the cursor, e.g.
 90 		// <p><strong>text</strong>^foo</p>
 91 		// <p>foo^<strong>text</strong></p>
 92 		// <div>^<p>foo</p></div>
 93 		if ( isTextCt( previous ) || isTextCt( next, 1 ) )
 94 			return true;
 95 
 96 		// Empty block/inline element is also affected. <span>^</span>, <p>^</p> (#7222)
 97 		if ( !( previous || next ) && !( ct.type == CKEDITOR.NODE_ELEMENT && ct.isBlockBoundary() && ct.getBogus() ) )
 98 			return true;
 99 
100 		return false;
101 	}
102 
103 	var selectAllCmd =
104 	{
105 		modes : { wysiwyg : 1, source : 1 },
106 		readOnly : CKEDITOR.env.ie || CKEDITOR.env.webkit,
107 		exec : function( editor )
108 		{
109 			switch ( editor.mode )
110 			{
111 				case 'wysiwyg' :
112 					editor.document.$.execCommand( 'SelectAll', false, null );
113 					// Force triggering selectionChange (#7008)
114 					editor.forceNextSelectionCheck();
115 					editor.selectionChange();
116 					break;
117 				case 'source' :
118 					// Select the contents of the textarea
119 					var textarea = editor.textarea.$;
120 					if ( CKEDITOR.env.ie )
121 						textarea.createTextRange().execCommand( 'SelectAll' );
122 					else
123 					{
124 						textarea.selectionStart = 0;
125 						textarea.selectionEnd = textarea.value.length;
126 					}
127 					textarea.focus();
128 			}
129 		},
130 		canUndo : false
131 	};
132 
133 	function createFillingChar( doc )
134 	{
135 		removeFillingChar( doc );
136 
137 		var fillingChar = doc.createText( '\u200B' );
138 		doc.setCustomData( 'cke-fillingChar', fillingChar );
139 
140 		return fillingChar;
141 	}
142 
143 	function getFillingChar( doc )
144 	{
145 		return doc && doc.getCustomData( 'cke-fillingChar' );
146 	}
147 
148 	// Checks if a filling char has been used, eventualy removing it (#1272).
149 	function checkFillingChar( doc )
150 	{
151 		var fillingChar = doc && getFillingChar( doc );
152 		if ( fillingChar )
153 		{
154 			// Use this flag to avoid removing the filling char right after
155 			// creating it.
156 			if ( fillingChar.getCustomData( 'ready' ) )
157 				removeFillingChar( doc );
158 			else
159 				fillingChar.setCustomData( 'ready', 1 );
160 		}
161 	}
162 
163 	function removeFillingChar( doc )
164 	{
165 		var fillingChar = doc && doc.removeCustomData( 'cke-fillingChar' );
166 		if ( fillingChar )
167 		{
168 			var bm,
169 			sel = doc.getSelection().getNative(),
170 			// Be error proof.
171 			range = sel && sel.type != 'None' && sel.getRangeAt( 0 );
172 
173 			// Text selection position might get mangled by
174 			// subsequent dom modification, save it now for restoring. (#8617)
175 			if ( fillingChar.getLength() > 1
176 				 && range && range.intersectsNode( fillingChar.$ ) )
177 			{
178 				bm = [ sel.anchorOffset, sel.focusOffset ];
179 
180 				// Anticipate the offset change brought by the removed char.
181 				var startAffected = sel.anchorNode == fillingChar.$ && sel.anchorOffset > 0,
182 					endAffected = sel.focusNode == fillingChar.$ && sel.focusOffset > 0;
183 				startAffected && bm[ 0 ]--;
184 				endAffected && bm[ 1 ]--;
185 
186 				// Revert the bookmark order on reverse selection.
187 				isReversedSelection( sel ) && bm.unshift( bm.pop() );
188 			}
189 
190 			// We can't simply remove the filling node because the user
191 			// will actually enlarge it when typing, so we just remove the
192 			// invisible char from it.
193 			fillingChar.setText( fillingChar.getText().replace( /\u200B/g, '' ) );
194 
195 			// Restore the bookmark.
196 			if ( bm )
197 			{
198 				var rng = sel.getRangeAt( 0 );
199 				rng.setStart( rng.startContainer, bm[ 0 ] );
200 				rng.setEnd( rng.startContainer, bm[ 1 ] );
201 				sel.removeAllRanges();
202 				sel.addRange( rng );
203 			}
204 		}
205 	}
206 
207 	function isReversedSelection( sel )
208 	{
209 		if ( !sel.isCollapsed )
210 		{
211 			var range = sel.getRangeAt( 0 );
212 			// Potentially alter an reversed selection range.
213 			range.setStart( sel.anchorNode, sel.anchorOffset );
214 			range.setEnd( sel.focusNode, sel.focusOffset );
215 			return range.collapsed;
216 		}
217 	}
218 
219 	CKEDITOR.plugins.add( 'selection',
220 	{
221 		init : function( editor )
222 		{
223 			// On WebKit only, we need a special "filling" char on some situations
224 			// (#1272). Here we set the events that should invalidate that char.
225 			if ( CKEDITOR.env.webkit )
226 			{
227 				editor.on( 'selectionChange', function() { checkFillingChar( editor.document ); } );
228 				editor.on( 'beforeSetMode', function() { removeFillingChar( editor.document ); } );
229 
230 				var fillingCharBefore,
231 					resetSelection;
232 
233 				function beforeData()
234 				{
235 					var doc = editor.document,
236 						fillingChar = getFillingChar( doc );
237 
238 					if ( fillingChar )
239 					{
240 						// If cursor is right blinking by side of the filler node, save it for restoring,
241 						// as the following text substitution will blind it. (#7437)
242 						var sel = doc.$.defaultView.getSelection();
243 						if ( sel.type == 'Caret' && sel.anchorNode == fillingChar.$ )
244 							resetSelection = 1;
245 
246 						fillingCharBefore = fillingChar.getText();
247 						fillingChar.setText( fillingCharBefore.replace( /\u200B/g, '' ) );
248 					}
249 				}
250 				function afterData()
251 				{
252 					var doc = editor.document,
253 						fillingChar = getFillingChar( doc );
254 
255 					if ( fillingChar )
256 					{
257 						fillingChar.setText( fillingCharBefore );
258 
259 						if ( resetSelection )
260 						{
261 							doc.$.defaultView.getSelection().setPosition( fillingChar.$,fillingChar.getLength() );
262 							resetSelection = 0;
263 						}
264 					}
265 				}
266 				editor.on( 'beforeUndoImage', beforeData );
267 				editor.on( 'afterUndoImage', afterData );
268 				editor.on( 'beforeGetData', beforeData, null, null, 0 );
269 				editor.on( 'getData', afterData );
270 			}
271 
272 			editor.on( 'contentDom', function()
273 				{
274 					var doc = editor.document,
275 						outerDoc = CKEDITOR.document,
276 						body = doc.getBody(),
277 						html = doc.getDocumentElement();
278 
279 					if ( CKEDITOR.env.ie )
280 					{
281 						// Other browsers don't loose the selection if the
282 						// editor document loose the focus. In IE, we don't
283 						// have support for it, so we reproduce it here, other
284 						// than firing the selection change event.
285 
286 						var savedRange,
287 							saveEnabled,
288 							restoreEnabled = 1;
289 
290 						// "onfocusin" is fired before "onfocus". It makes it
291 						// possible to restore the selection before click
292 						// events get executed.
293 						body.on( 'focusin', function( evt )
294 							{
295 								// If there are elements with layout they fire this event but
296 								// it must be ignored to allow edit its contents #4682
297 								if ( evt.data.$.srcElement.nodeName != 'BODY' )
298 									return;
299 
300 								// Give the priority to locked selection since it probably
301 								// reflects the actual situation, besides locked selection
302 								// could be interfered because of text nodes normalizing.
303 								// (#6083, #6987)
304 								var lockedSelection = doc.getCustomData( 'cke_locked_selection' );
305 								if ( lockedSelection )
306 								{
307 									lockedSelection.unlock( 1 );
308 									lockedSelection.lock();
309 								}
310 								// Then check ff we have saved a range, restore it at this
311 								// point.
312 								else if ( savedRange && restoreEnabled )
313 								{
314 									// Well not break because of this.
315 									try { savedRange.select(); } catch (e) {}
316 									savedRange = null;
317 								}
318 							});
319 
320 						body.on( 'focus', function()
321 							{
322 								// Enable selections to be saved.
323 								saveEnabled = 1;
324 
325 								saveSelection();
326 							});
327 
328 						body.on( 'beforedeactivate', function( evt )
329 							{
330 								// Ignore this event if it's caused by focus switch between
331 								// internal editable control type elements, e.g. layouted paragraph. (#4682)
332 								if ( evt.data.$.toElement )
333 									return;
334 
335 								// Disable selections from being saved.
336 								saveEnabled = 0;
337 								restoreEnabled = 1;
338 							});
339 
340 						// [IE] Iframe will still keep the selection when blurred, if
341 						// focus is moved onto a non-editing host, e.g. link or button, but
342 						// it becomes a problem for the object type selection, since the resizer
343 						// handler attached on it will mark other part of the UI, especially
344 						// for the dialog. (#8157)
345 						// [IE<8] Even worse For old IEs, the cursor will not vanish even if
346 						// the selection has been moved to another text input in some cases. (#4716)
347 						//
348 						// Now the range restore is disabled, so we simply force IE to clean
349 						// up the selection before blur.
350 						CKEDITOR.env.ie && editor.on( 'blur', function()
351 						{
352 							// Error proof when the editor is not visible. (#6375)
353 							try{ doc.$.selection.empty(); } catch ( er){}
354 						});
355 
356 						// Listening on document element ensures that
357 						// scrollbar is included. (#5280)
358 						html.on( 'mousedown', function()
359 						{
360 							// Lock restore selection now, as we have
361 							// a followed 'click' event which introduce
362 							// new selection. (#5735)
363 							restoreEnabled = 0;
364 						});
365 
366 						html.on( 'mouseup', function()
367 						{
368 							restoreEnabled = 1;
369 						});
370 
371 						var scroll;
372 						// IE fires the "selectionchange" event when clicking
373 						// inside a selection. We don't want to capture that.
374 						body.on( 'mousedown', function( evt )
375 						{
376 							// IE scrolls document to top on right mousedown
377 							// when editor has no focus, remember this scroll
378 							// position and revert it before context menu opens. (#5778)
379 							if ( evt.data.$.button == 2 )
380 							{
381 								var sel = editor.document.$.selection;
382 								if ( sel.type == 'None' )
383 									scroll = editor.window.getScrollPosition();
384 							}
385 							disableSave();
386 						});
387 
388 						body.on( 'mouseup',
389 							function( evt )
390 							{
391 								// Restore recorded scroll position when needed on right mouseup.
392 								if ( evt.data.$.button == 2 && scroll )
393 								{
394 									editor.document.$.documentElement.scrollLeft = scroll.x;
395 									editor.document.$.documentElement.scrollTop = scroll.y;
396 								}
397 								scroll = null;
398 
399 								saveEnabled = 1;
400 								setTimeout( function()
401 									{
402 										saveSelection( true );
403 									},
404 									0 );
405 							});
406 
407 						body.on( 'keydown', disableSave );
408 						body.on( 'keyup',
409 							function()
410 							{
411 								saveEnabled = 1;
412 								saveSelection();
413 							});
414 
415 						// When content doc is in standards mode, IE doesn't produce text selection
416 						// when click on the region outside of body, we emulate
417 						// the correct behavior here. (#1659, #7932, # 9097)
418 						if ( doc.$.compatMode != 'BackCompat' )
419 						{
420 							if ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat )
421 							{
422 								function moveRangeToPoint( range, x, y )
423 								{
424 									// Error prune in IE7. (#9034, #9110)
425 									try { range.moveToPoint( x, y ); } catch ( e ) {}
426 								}
427 
428 								html.on( 'mousedown', function( evt )
429 								{
430 									// Expand the text range along with mouse move.
431 									function onHover( evt )
432 									{
433 										evt = evt.data.$;
434 										if ( textRng )
435 										{
436 											// Read the current cursor.
437 											var rngEnd = body.$.createTextRange();
438 
439 											moveRangeToPoint( rngEnd, evt.x, evt.y );
440 
441 											// Handle drag directions.
442 											textRng.setEndPoint(
443 												startRng.compareEndPoints( 'StartToStart', rngEnd ) < 0 ?
444 												'EndToEnd' :
445 												'StartToStart',
446 												rngEnd );
447 
448 											// Update selection with new range.
449 											textRng.select();
450 										}
451 									}
452 
453 									function removeListeners()
454 									{
455 										outerDoc.removeListener( 'mouseup', onSelectEnd );
456 										html.removeListener( 'mouseup', onSelectEnd );
457 									}
458 
459 									function onSelectEnd()
460 									{
461 
462 										html.removeListener( 'mousemove', onHover );
463 										removeListeners();
464 
465 										// Make it in effect on mouse up. (#9022)
466 										textRng.select();
467 									}
468 
469 									evt = evt.data;
470 
471 									// We're sure that the click happens at the region
472 									// outside body, but not on scrollbar.
473 									if ( evt.getTarget().is( 'html' ) &&
474 											 evt.$.x < html.$.clientWidth &&
475 											 evt.$.y < html.$.clientHeight )
476 									{
477 										// Start to build the text range.
478 										var textRng = body.$.createTextRange();
479 										moveRangeToPoint( textRng, evt.$.x, evt.$.y );
480 										// Records the dragging start of the above text range.
481 										var startRng = textRng.duplicate();
482 
483 										html.on( 'mousemove', onHover );
484 										outerDoc.on( 'mouseup', onSelectEnd );
485 										html.on( 'mouseup', onSelectEnd );
486 									}
487 								});
488 							}
489 
490 							// It's much simpler for IE > 8, we just need to reselect the reported range.
491 							if ( CKEDITOR.env.ie8 )
492 							{
493 								html.on( 'mousedown', function( evt )
494 								{
495 									if ( evt.data.getTarget().is( 'html' ) )
496 									{
497 										// Limit the text selection mouse move inside of editable. (#9715)
498 										outerDoc.on( 'mouseup', onSelectEnd );
499 										html.on( 'mouseup', onSelectEnd );
500 									}
501 
502 								});
503 
504 								function removeListeners()
505 								{
506 									outerDoc.removeListener( 'mouseup', onSelectEnd );
507 									html.removeListener( 'mouseup', onSelectEnd );
508 								}
509 
510 								function onSelectEnd()
511 								{
512 									removeListeners();
513 
514 									// The event is not fired when clicking on the scrollbars,
515 									// so we can safely check the following to understand
516 									// whether the empty space following <body> has been clicked.
517 										var sel = CKEDITOR.document.$.selection,
518 											range = sel.createRange();
519 										// The selection range is reported on host, but actually it should applies to the content doc.
520 										if ( sel.type != 'None' && range.parentElement().ownerDocument == doc.$ )
521 											range.select();
522 								}
523 							}
524 
525 						}
526 						// IE is the only to provide the "selectionchange"
527 						// event.
528 						doc.on( 'selectionchange', saveSelection );
529 
530 						function disableSave()
531 						{
532 							saveEnabled = 0;
533 						}
534 
535 						function saveSelection( testIt )
536 						{
537 							if ( saveEnabled )
538 							{
539 								var doc = editor.document,
540 									sel = editor.getSelection(),
541 									nativeSel = sel && sel.getNative();
542 
543 								// There is a very specific case, when clicking
544 								// inside a text selection. In that case, the
545 								// selection collapses at the clicking point,
546 								// but the selection object remains in an
547 								// unknown state, making createRange return a
548 								// range at the very start of the document. In
549 								// such situation we have to test the range, to
550 								// be sure it's valid.
551 								if ( testIt && nativeSel && nativeSel.type == 'None' )
552 								{
553 									// The "InsertImage" command can be used to
554 									// test whether the selection is good or not.
555 									// If not, it's enough to give some time to
556 									// IE to put things in order for us.
557 									if ( !doc.$.queryCommandEnabled( 'InsertImage' ) )
558 									{
559 										CKEDITOR.tools.setTimeout( saveSelection, 50, this, true );
560 										return;
561 									}
562 								}
563 
564 								// Avoid saving selection from within text input. (#5747)
565 								var parentTag;
566 								if ( nativeSel && nativeSel.type && nativeSel.type != 'Control'
567 									&& ( parentTag = nativeSel.createRange() )
568 									&& ( parentTag = parentTag.parentElement() )
569 									&& ( parentTag = parentTag.nodeName )
570 									&& parentTag.toLowerCase() in { input: 1, textarea : 1 } )
571 								{
572 									return;
573 								}
574 
575 								// Not break because of this. (#9132)
576 								try{ savedRange = nativeSel && sel.getRanges()[ 0 ]; } catch( er ) {}
577 
578 								checkSelectionChangeTimeout.call( editor );
579 							}
580 						}
581 					}
582 					else
583 					{
584 						// In other browsers, we make the selection change
585 						// check based on other events, like clicks or keys
586 						// press.
587 
588 						doc.on( 'mouseup', checkSelectionChangeTimeout, editor );
589 						doc.on( 'keyup', checkSelectionChangeTimeout, editor );
590 						doc.on( 'selectionchange', checkSelectionChangeTimeout, editor );
591 					}
592 
593 					if ( CKEDITOR.env.webkit )
594 					{
595 						// Before keystroke is handled by editor, check to remove the filling char.
596 						doc.on( 'keydown', function( evt )
597 						{
598 							var key = evt.data.getKey();
599 							// Remove the filling char before some keys get
600 							// executed, so they'll not get blocked by it.
601 							switch ( key )
602 							{
603 								case 13 :	// ENTER
604 								case 33 :	// PAGEUP
605 								case 34 :	// PAGEDOWN
606 								case 35 :	// HOME
607 								case 36 :	// END
608 								case 37 :	// LEFT-ARROW
609 								case 39 :	// RIGHT-ARROW
610 								case 8 :	// BACKSPACE
611 								case 45 :	// INS
612 								case 46 :	// DEl
613 									removeFillingChar( editor.document );
614 							}
615 
616 						}, null, null, -1 );
617 					}
618 				});
619 
620 			// Clear the cached range path before unload. (#7174)
621 			editor.on( 'contentDomUnload', editor.forceNextSelectionCheck, editor );
622 
623 			editor.addCommand( 'selectAll', selectAllCmd );
624 			editor.ui.addButton( 'SelectAll',
625 				{
626 					label : editor.lang.selectAll,
627 					command : 'selectAll'
628 				});
629 
630 			/**
631 			 * Check if to fire the {@link CKEDITOR.editor#selectionChange} event
632 			 * for the current editor instance.
633 			 *
634 			 * @param {Boolean} checkNow Check immediately without any delay.
635 			 */
636 			editor.selectionChange = function( checkNow )
637 			{
638 				( checkNow ? checkSelectionChange : checkSelectionChangeTimeout ).call( this );
639 			};
640 
641 			// IE9 might cease to work if there's an object selection inside the iframe (#7639).
642 			CKEDITOR.env.ie9Compat && editor.on( 'destroy', function()
643 			{
644 				var sel = editor.getSelection();
645 				sel && sel.getNative().clear();
646 			}, null, null, 9 );
647 		}
648 	});
649 
650 	/**
651 	 * Gets the current selection from the editing area when in WYSIWYG mode.
652 	 * @returns {CKEDITOR.dom.selection} A selection object or null if not in
653 	 *		WYSIWYG mode or no selection is available.
654 	 * @example
655 	 * var selection = CKEDITOR.instances.editor1.<strong>getSelection()</strong>;
656 	 * alert( selection.getType() );
657 	 */
658 	CKEDITOR.editor.prototype.getSelection = function()
659 	{
660 		return this.document && this.document.getSelection();
661 	};
662 
663 	CKEDITOR.editor.prototype.forceNextSelectionCheck = function()
664 	{
665 		delete this._.selectionPreviousPath;
666 	};
667 
668 	/**
669 	 * Gets the current selection from the document.
670 	 * @returns {CKEDITOR.dom.selection} A selection object.
671 	 * @example
672 	 * var selection = CKEDITOR.instances.editor1.document.<strong>getSelection()</strong>;
673 	 * alert( selection.getType() );
674 	 */
675 	CKEDITOR.dom.document.prototype.getSelection = function()
676 	{
677 		var sel = new CKEDITOR.dom.selection( this );
678 		return ( !sel || sel.isInvalid ) ? null : sel;
679 	};
680 
681 	/**
682 	 * No selection.
683 	 * @constant
684 	 * @example
685 	 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_NONE )
686 	 *     alert( 'Nothing is selected' );
687 	 */
688 	CKEDITOR.SELECTION_NONE		= 1;
689 
690 	/**
691 	 * A text or a collapsed selection.
692 	 * @constant
693 	 * @example
694 	 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_TEXT )
695 	 *     alert( 'A text is selected' );
696 	 */
697 	CKEDITOR.SELECTION_TEXT		= 2;
698 
699 	/**
700 	 * Element selection.
701 	 * @constant
702 	 * @example
703 	 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_ELEMENT )
704 	 *     alert( 'An element is selected' );
705 	 */
706 	CKEDITOR.SELECTION_ELEMENT	= 3;
707 
708 	var isMSSelection = CKEDITOR.env.ie && CKEDITOR.env.version < 10;
709 
710 	/**
711 	 * Manipulates the selection in a DOM document.
712 	 * @constructor
713 	 * @param {CKEDITOR.dom.document} document The DOM document that contains the selection.
714 	 * @example
715 	 * var sel = new <strong>CKEDITOR.dom.selection( CKEDITOR.document )</strong>;
716 	 */
717 	CKEDITOR.dom.selection = function( document )
718 	{
719 		var lockedSelection = document.getCustomData( 'cke_locked_selection' );
720 
721 		if ( lockedSelection )
722 			return lockedSelection;
723 
724 		this.document = document;
725 		this.isLocked = 0;
726 		this._ =
727 		{
728 			cache : {}
729 		};
730 
731 		/**
732 		 * IE BUG: The selection's document may be a different document than the
733 		 * editor document. Return null if that is the case.
734 		 */
735 		if ( isMSSelection )
736 		{
737 			// Avoid breaking because of it. (#8836)
738 			try
739 			{
740 				var range = this.getNative().createRange();
741 				if ( !range ||
742 					 ( range.item && range.item( 0 ).ownerDocument != this.document.$ ) ||
743 					 ( range.parentElement && range.parentElement().ownerDocument != this.document.$ ) )
744 				{
745 					throw 0;
746 				}
747 			}
748 			catch ( e )
749 			{
750 				this.isInvalid = true;
751 			}
752 		}
753 
754 		return this;
755 	};
756 
757 	var styleObjectElements =
758 		{
759 			img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1,
760 			a:1,input:1,form:1,select:1,textarea:1,button:1,fieldset:1,thead:1,tfoot:1
761 		};
762 
763 	CKEDITOR.dom.selection.prototype =
764 	{
765 		/**
766 		 * Gets the native selection object from the browser.
767 		 * @function
768 		 * @returns {Object} The native browser selection object.
769 		 * @example
770 		 * var selection = editor.getSelection().<strong>getNative()</strong>;
771 		 */
772 		getNative : function()
773 		{
774 			if ( this._.cache.nativeSel !== undefined )
775 				return this._.cache.nativeSel;
776 
777 			return ( this._.cache.nativeSel = isMSSelection ? this.document.$.selection : this.document.getWindow().$.getSelection() );
778 		},
779 
780 		/**
781 		 * Gets the type of the current selection. The following values are
782 		 * available:
783 		 * <ul>
784 		 *		<li><code>{@link CKEDITOR.SELECTION_NONE}</code> (1): No selection.</li>
785 		 *		<li><code>{@link CKEDITOR.SELECTION_TEXT}</code> (2): A text or a collapsed
786 		 *			selection is selected.</li>
787 		 *		<li><code>{@link CKEDITOR.SELECTION_ELEMENT}</code> (3): An element is
788 		 *			selected.</li>
789 		 * </ul>
790 		 * @function
791 		 * @returns {Number} One of the following constant values:
792 		 *		<code>{@link CKEDITOR.SELECTION_NONE}</code>, <code>{@link CKEDITOR.SELECTION_TEXT}</code>, or
793 		 *		<code>{@link CKEDITOR.SELECTION_ELEMENT}</code>.
794 		 * @example
795 		 * if ( editor.getSelection().<strong>getType()</strong> == CKEDITOR.SELECTION_TEXT )
796 		 *     alert( 'A text is selected' );
797 		 */
798 		getType :
799 			isMSSelection ?
800 				function()
801 				{
802 					var cache = this._.cache;
803 					if ( cache.type )
804 						return cache.type;
805 
806 					var type = CKEDITOR.SELECTION_NONE;
807 
808 					try
809 					{
810 						var sel = this.getNative(),
811 							ieType = sel.type;
812 
813 						if ( ieType == 'Text' )
814 							type = CKEDITOR.SELECTION_TEXT;
815 
816 						if ( ieType == 'Control' )
817 							type = CKEDITOR.SELECTION_ELEMENT;
818 
819 						// It is possible that we can still get a text range
820 						// object even when type == 'None' is returned by IE.
821 						// So we'd better check the object returned by
822 						// createRange() rather than by looking at the type.
823 						if ( sel.createRange().parentElement )
824 							type = CKEDITOR.SELECTION_TEXT;
825 					}
826 					catch(e) {}
827 
828 					return ( cache.type = type );
829 				}
830 			:
831 				function()
832 				{
833 					var cache = this._.cache;
834 					if ( cache.type )
835 						return cache.type;
836 
837 					var type = CKEDITOR.SELECTION_TEXT;
838 
839 					var sel = this.getNative();
840 
841 					if ( !sel )
842 						type = CKEDITOR.SELECTION_NONE;
843 					else if ( sel.rangeCount == 1 )
844 					{
845 						// Check if the actual selection is a control (IMG,
846 						// TABLE, HR, etc...).
847 
848 						var range = sel.getRangeAt(0),
849 							startContainer = range.startContainer;
850 
851 						if ( startContainer == range.endContainer
852 							&& startContainer.nodeType == 1
853 							&& ( range.endOffset - range.startOffset ) == 1
854 							&& styleObjectElements[ startContainer.childNodes[ range.startOffset ].nodeName.toLowerCase() ] )
855 						{
856 							type = CKEDITOR.SELECTION_ELEMENT;
857 						}
858 					}
859 
860 					return ( cache.type = type );
861 				},
862 
863 		/**
864 		 * Retrieves the <code>{@link CKEDITOR.dom.range}</code> instances that represent the current selection.
865 		 * Note: Some browsers return multiple ranges even for a continuous selection. Firefox, for example, returns
866 		 * one range for each table cell when one or more table rows are selected.
867 		 * @function
868 		 * @param {Boolean} [onlyEditables] If set to <code>true</code>, this function retrives editable ranges only.
869 		 * @return {Array} Range instances that represent the current selection.
870 		 * @example
871 		 * var ranges = selection.<strong>getRanges()</strong>;
872 		 * alert( ranges.length );
873 		 */
874 		getRanges : (function()
875 		{
876 			var func = isMSSelection ?
877 				( function()
878 				{
879 					function getNodeIndex( node ) { return new CKEDITOR.dom.node( node ).getIndex(); }
880 
881 					// Finds the container and offset for a specific boundary
882 					// of an IE range.
883 					var getBoundaryInformation = function( range, start )
884 					{
885 						// Creates a collapsed range at the requested boundary.
886 						range = range.duplicate();
887 						range.collapse( start );
888 
889 						// Gets the element that encloses the range entirely.
890 						var parent = range.parentElement(),
891 							doc = parent.ownerDocument;
892 
893 						// Empty parent element, e.g. <i>^</i>
894 						if ( !parent.hasChildNodes() )
895 							return  { container : parent, offset : 0 };
896 
897 						var siblings = parent.children,
898 							child,
899 							sibling,
900 							testRange = range.duplicate(),
901 							startIndex = 0,
902 							endIndex = siblings.length - 1,
903 							index = -1,
904 							position,
905 							distance,
906 							container;
907 
908 						// Binary search over all element childs to test the range to see whether
909 						// range is right on the boundary of one element.
910 						while ( startIndex <= endIndex )
911 						{
912 							index = Math.floor( ( startIndex + endIndex ) / 2 );
913 							child = siblings[ index ];
914 							testRange.moveToElementText( child );
915 							position = testRange.compareEndPoints( 'StartToStart', range );
916 
917 							if ( position > 0 )
918 								endIndex = index - 1;
919 							else if ( position < 0 )
920 								startIndex = index + 1;
921 							else
922 							{
923 								// IE9 report wrong measurement with compareEndPoints when range anchors between two BRs.
924 								// e.g. <p>text<br />^<br /></p> (#7433)
925 								if ( CKEDITOR.env.ie9Compat && child.tagName == 'BR' )
926 								{
927 									// "Fall back" to w3c selection.
928 									var sel = doc.defaultView.getSelection();
929 									return { container : sel[ start ? 'anchorNode' : 'focusNode' ],
930 										offset : sel[ start ? 'anchorOffset' : 'focusOffset' ] };
931 								}
932 								else
933 									return { container : parent, offset : getNodeIndex( child ) };
934 							}
935 						}
936 
937 						// All childs are text nodes,
938 						// or to the right hand of test range are all text nodes. (#6992)
939 						if ( index == -1 || index == siblings.length - 1 && position < 0 )
940 						{
941 							// Adapt test range to embrace the entire parent contents.
942 							testRange.moveToElementText( parent );
943 							testRange.setEndPoint( 'StartToStart', range );
944 
945 							// IE report line break as CRLF with range.text but
946 							// only LF with textnode.nodeValue, normalize them to avoid
947 							// breaking character counting logic below. (#3949)
948 							distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length;
949 
950 							siblings = parent.childNodes;
951 
952 							// Actual range anchor right beside test range at the boundary of text node.
953 							if ( !distance )
954 							{
955 								child = siblings[ siblings.length - 1 ];
956 
957 								if ( child.nodeType != CKEDITOR.NODE_TEXT )
958 									return { container : parent, offset : siblings.length };
959 								else
960 									return { container : child, offset : child.nodeValue.length };
961 							}
962 
963 							// Start the measuring until distance overflows, meanwhile count the text nodes.
964 							var i = siblings.length;
965 							while ( distance > 0 && i > 0 )
966 							{
967 								sibling = siblings[ --i ];
968 								if ( sibling.nodeType == CKEDITOR.NODE_TEXT )
969 								{
970 									container = sibling;
971 									distance -= sibling.nodeValue.length;
972 								}
973 							}
974 
975 							return  { container : container, offset : -distance };
976 						}
977 						// Test range was one offset beyond OR behind the anchored text node.
978 						else
979 						{
980 							// Adapt one side of test range to the actual range
981 							// for measuring the offset between them.
982 							testRange.collapse( position > 0 ? true : false );
983 							testRange.setEndPoint( position > 0 ? 'StartToStart' : 'EndToStart', range );
984 
985 							// IE report line break as CRLF with range.text but
986 							// only LF with textnode.nodeValue, normalize them to avoid
987 							// breaking character counting logic below. (#3949)
988 							distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length;
989 
990 							// Actual range anchor right beside test range at the inner boundary of text node.
991 							if ( !distance )
992 								return { container : parent, offset : getNodeIndex( child ) + ( position > 0 ? 0 : 1 ) };
993 
994 							// Start the measuring until distance overflows, meanwhile count the text nodes.
995 							while ( distance > 0 )
996 							{
997 								try
998 								{
999 									sibling = child[ position > 0 ? 'previousSibling' : 'nextSibling' ];
1000 									if ( sibling.nodeType == CKEDITOR.NODE_TEXT )
1001 									{
1002 										distance -= sibling.nodeValue.length;
1003 										container = sibling;
1004 									}
1005 									child = sibling;
1006 								}
1007 								// Measurement in IE could be somtimes wrong because of <select> element. (#4611)
1008 								catch( e )
1009 								{
1010 									return { container : parent, offset : getNodeIndex( child ) };
1011 								}
1012 							}
1013 
1014 							return { container : container, offset : position > 0 ? -distance : container.nodeValue.length + distance };
1015 						}
1016 					};
1017 
1018 					return function()
1019 					{
1020 						// IE doesn't have range support (in the W3C way), so we
1021 						// need to do some magic to transform selections into
1022 						// CKEDITOR.dom.range instances.
1023 
1024 						var sel = this.getNative(),
1025 							nativeRange = sel && sel.createRange(),
1026 							type = this.getType(),
1027 							range;
1028 
1029 						if ( !sel )
1030 							return [];
1031 
1032 						if ( type == CKEDITOR.SELECTION_TEXT )
1033 						{
1034 							range = new CKEDITOR.dom.range( this.document );
1035 
1036 							var boundaryInfo = getBoundaryInformation( nativeRange, true );
1037 							range.setStart( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset );
1038 
1039 							boundaryInfo = getBoundaryInformation( nativeRange );
1040 							range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset );
1041 
1042 							// Correct an invalid IE range case on empty list item. (#5850)
1043 							if ( range.endContainer.getPosition( range.startContainer ) & CKEDITOR.POSITION_PRECEDING
1044 									&& range.endOffset <= range.startContainer.getIndex() )
1045 							{
1046 								range.collapse();
1047 							}
1048 
1049 							return [ range ];
1050 						}
1051 						else if ( type == CKEDITOR.SELECTION_ELEMENT )
1052 						{
1053 							var retval = [];
1054 
1055 							for ( var i = 0 ; i < nativeRange.length ; i++ )
1056 							{
1057 								var element = nativeRange.item( i ),
1058 									parentElement = element.parentNode,
1059 									j = 0;
1060 
1061 								range = new CKEDITOR.dom.range( this.document );
1062 
1063 								for (; j < parentElement.childNodes.length && parentElement.childNodes[j] != element ; j++ )
1064 								{ /*jsl:pass*/ }
1065 
1066 								range.setStart( new CKEDITOR.dom.node( parentElement ), j );
1067 								range.setEnd( new CKEDITOR.dom.node( parentElement ), j + 1 );
1068 								retval.push( range );
1069 							}
1070 
1071 							return retval;
1072 						}
1073 
1074 						return [];
1075 					};
1076 				})()
1077 			:
1078 				function()
1079 				{
1080 
1081 					// On browsers implementing the W3C range, we simply
1082 					// tranform the native ranges in CKEDITOR.dom.range
1083 					// instances.
1084 
1085 					var ranges = [],
1086 						range,
1087 						doc = this.document,
1088 						sel = this.getNative();
1089 
1090 					if ( !sel )
1091 						return ranges;
1092 
1093 					// On WebKit, it may happen that we'll have no selection
1094 					// available. We normalize it here by replicating the
1095 					// behavior of other browsers.
1096 					if ( !sel.rangeCount )
1097 					{
1098 						range = new CKEDITOR.dom.range( doc );
1099 						range.moveToElementEditStart( doc.getBody() );
1100 						ranges.push( range );
1101 					}
1102 
1103 					for ( var i = 0 ; i < sel.rangeCount ; i++ )
1104 					{
1105 						var nativeRange = sel.getRangeAt( i );
1106 
1107 						range = new CKEDITOR.dom.range( doc );
1108 
1109 						range.setStart( new CKEDITOR.dom.node( nativeRange.startContainer ), nativeRange.startOffset );
1110 						range.setEnd( new CKEDITOR.dom.node( nativeRange.endContainer ), nativeRange.endOffset );
1111 						ranges.push( range );
1112 					}
1113 					return ranges;
1114 				};
1115 
1116 			return function( onlyEditables )
1117 			{
1118 				var cache = this._.cache;
1119 				if ( cache.ranges && !onlyEditables )
1120 					return cache.ranges;
1121 				else if ( !cache.ranges )
1122 					cache.ranges = new CKEDITOR.dom.rangeList( func.call( this ) );
1123 
1124 				// Split range into multiple by read-only nodes.
1125 				if ( onlyEditables )
1126 				{
1127 					var ranges = cache.ranges;
1128 					for ( var i = 0; i < ranges.length; i++ )
1129 					{
1130 						var range = ranges[ i ];
1131 
1132 						// Drop range spans inside one ready-only node.
1133 						var parent = range.getCommonAncestor();
1134 						if ( parent.isReadOnly() )
1135 							ranges.splice( i, 1 );
1136 
1137 						if ( range.collapsed )
1138 							continue;
1139 
1140 						// Range may start inside a non-editable element,
1141 						// replace the range start after it.
1142 						if ( range.startContainer.isReadOnly() )
1143 						{
1144 							var current = range.startContainer;
1145 							while( current )
1146 							{
1147 								if ( current.is( 'body' ) || !current.isReadOnly() )
1148 									break;
1149 
1150 								if ( current.type == CKEDITOR.NODE_ELEMENT
1151 										&& current.getAttribute( 'contentEditable' ) == 'false' )
1152 									range.setStartAfter( current );
1153 
1154 								current = current.getParent();
1155 							}
1156 						}
1157 
1158 						var startContainer = range.startContainer,
1159 							endContainer = range.endContainer,
1160 							startOffset = range.startOffset,
1161 							endOffset = range.endOffset,
1162 							walkerRange = range.clone();
1163 
1164 						// Enlarge range start/end with text node to avoid walker
1165 						// being DOM destructive, it doesn't interfere our checking
1166 						// of elements below as well.
1167 						if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
1168 						{
1169 							if ( startOffset >= startContainer.getLength() )
1170 								walkerRange.setStartAfter( startContainer );
1171 							else
1172 								walkerRange.setStartBefore( startContainer );
1173 						}
1174 
1175 						if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
1176 						{
1177 							if ( !endOffset )
1178 								walkerRange.setEndBefore( endContainer );
1179 							else
1180 								walkerRange.setEndAfter( endContainer );
1181 						}
1182 
1183 						// Looking for non-editable element inside the range.
1184 						var walker = new CKEDITOR.dom.walker( walkerRange );
1185 						walker.evaluator = function( node )
1186 						{
1187 							if ( node.type == CKEDITOR.NODE_ELEMENT
1188 								&& node.isReadOnly() )
1189 							{
1190 								var newRange = range.clone();
1191 								range.setEndBefore( node );
1192 
1193 								// Drop collapsed range around read-only elements,
1194 								// it make sure the range list empty when selecting
1195 								// only non-editable elements.
1196 								if ( range.collapsed )
1197 									ranges.splice( i--, 1 );
1198 
1199 								// Avoid creating invalid range.
1200 								if ( !( node.getPosition( walkerRange.endContainer ) & CKEDITOR.POSITION_CONTAINS ) )
1201 								{
1202 									newRange.setStartAfter( node );
1203 									if ( !newRange.collapsed )
1204 										ranges.splice( i + 1, 0, newRange );
1205 								}
1206 
1207 								return true;
1208 							}
1209 
1210 							return false;
1211 						};
1212 
1213 						walker.next();
1214 					}
1215 				}
1216 
1217 				return cache.ranges;
1218 			};
1219 		})(),
1220 
1221 		/**
1222 		 * Gets the DOM element in which the selection starts.
1223 		 * @returns {CKEDITOR.dom.element} The element at the beginning of the
1224 		 *		selection.
1225 		 * @example
1226 		 * var element = editor.getSelection().<strong>getStartElement()</strong>;
1227 		 * alert( element.getName() );
1228 		 */
1229 		getStartElement : function()
1230 		{
1231 			var cache = this._.cache;
1232 			if ( cache.startElement !== undefined )
1233 				return cache.startElement;
1234 
1235 			var node,
1236 				sel = this.getNative();
1237 
1238 			switch ( this.getType() )
1239 			{
1240 				case CKEDITOR.SELECTION_ELEMENT :
1241 					return this.getSelectedElement();
1242 
1243 				case CKEDITOR.SELECTION_TEXT :
1244 
1245 					var range = this.getRanges()[0];
1246 
1247 					if ( range )
1248 					{
1249 						if ( !range.collapsed )
1250 						{
1251 							range.optimize();
1252 
1253 							// Decrease the range content to exclude particial
1254 							// selected node on the start which doesn't have
1255 							// visual impact. ( #3231 )
1256 							while ( 1 )
1257 							{
1258 								var startContainer = range.startContainer,
1259 									startOffset = range.startOffset;
1260 								// Limit the fix only to non-block elements.(#3950)
1261 								if ( startOffset == ( startContainer.getChildCount ?
1262 									 startContainer.getChildCount() : startContainer.getLength() )
1263 									 && !startContainer.isBlockBoundary() )
1264 									range.setStartAfter( startContainer );
1265 								else break;
1266 							}
1267 
1268 							node = range.startContainer;
1269 
1270 							if ( node.type != CKEDITOR.NODE_ELEMENT )
1271 								return node.getParent();
1272 
1273 							node = node.getChild( range.startOffset );
1274 
1275 							if ( !node || node.type != CKEDITOR.NODE_ELEMENT )
1276 								node = range.startContainer;
1277 							else
1278 							{
1279 								var child = node.getFirst();
1280 								while (  child && child.type == CKEDITOR.NODE_ELEMENT )
1281 								{
1282 									node = child;
1283 									child = child.getFirst();
1284 								}
1285 							}
1286 						}
1287 						else
1288 						{
1289 							node = range.startContainer;
1290 							if ( node.type != CKEDITOR.NODE_ELEMENT )
1291 								node = node.getParent();
1292 						}
1293 
1294 						node = node.$;
1295 					}
1296 			}
1297 
1298 			return cache.startElement = ( node ? new CKEDITOR.dom.element( node ) : null );
1299 		},
1300 
1301 		/**
1302 		 * Gets the currently selected element.
1303 		 * @returns {CKEDITOR.dom.element} The selected element. Null if no
1304 		 *		selection is available or the selection type is not
1305 		 *		<code>{@link CKEDITOR.SELECTION_ELEMENT}</code>.
1306 		 * @example
1307 		 * var element = editor.getSelection().<strong>getSelectedElement()</strong>;
1308 		 * alert( element.getName() );
1309 		 */
1310 		getSelectedElement : function()
1311 		{
1312 			var cache = this._.cache;
1313 			if ( cache.selectedElement !== undefined )
1314 				return cache.selectedElement;
1315 
1316 			var self = this;
1317 
1318 			var node = CKEDITOR.tools.tryThese(
1319 				// Is it native IE control type selection?
1320 				function()
1321 				{
1322 					return self.getNative().createRange().item( 0 );
1323 				},
1324 				// If a table or list is fully selected.
1325 				function()
1326 				{
1327 					var root,
1328 						retval,
1329 						range  = self.getRanges()[ 0 ],
1330 						ancestor = range.getCommonAncestor( 1, 1 ),
1331 						tags = { table:1,ul:1,ol:1,dl:1 };
1332 
1333 					for ( var t in tags )
1334 					{
1335 						if ( ( root = ancestor.getAscendant( t, 1 ) ) )
1336 							break;
1337 					}
1338 
1339 					if ( root )
1340 					{
1341 						// Enlarging the start boundary.
1342 						var testRange = new CKEDITOR.dom.range( this.document );
1343 						testRange.setStartAt( root, CKEDITOR.POSITION_AFTER_START );
1344 						testRange.setEnd( range.startContainer, range.startOffset );
1345 
1346 						var enlargeables = CKEDITOR.tools.extend( tags, CKEDITOR.dtd.$listItem, CKEDITOR.dtd.$tableContent ),
1347 							walker = new CKEDITOR.dom.walker( testRange ),
1348 							// Check the range is at the inner boundary of the structural element.
1349 							guard = function( walker, isEnd )
1350 							{
1351 								return function( node, isWalkOut )
1352 								{
1353 									if ( node.type == CKEDITOR.NODE_TEXT && ( !CKEDITOR.tools.trim( node.getText() ) || node.getParent().data( 'cke-bookmark' ) ) )
1354 										return true;
1355 
1356 									var tag;
1357 									if ( node.type == CKEDITOR.NODE_ELEMENT )
1358 									{
1359 										tag = node.getName();
1360 
1361 										// Bypass bogus br at the end of block.
1362 										if ( tag == 'br' && isEnd && node.equals( node.getParent().getBogus() ) )
1363 											return true;
1364 
1365 										if ( isWalkOut && tag in enlargeables || tag in CKEDITOR.dtd.$removeEmpty )
1366 											return true;
1367 									}
1368 
1369 									walker.halted = 1;
1370 									return false;
1371 								};
1372 							};
1373 
1374 						walker.guard = guard( walker );
1375 
1376 						if ( walker.checkBackward() && !walker.halted )
1377 						{
1378 							walker = new CKEDITOR.dom.walker( testRange );
1379 							testRange.setStart( range.endContainer, range.endOffset );
1380 							testRange.setEndAt( root, CKEDITOR.POSITION_BEFORE_END );
1381 							walker.guard = guard( walker, 1 );
1382 							if ( walker.checkForward() && !walker.halted )
1383 								retval = root.$;
1384 						}
1385 					}
1386 
1387 					if ( !retval )
1388 						throw 0;
1389 
1390 					return retval;
1391 				},
1392 				// Figure it out by checking if there's a single enclosed
1393 				// node of the range.
1394 				function()
1395 				{
1396 					var range  = self.getRanges()[ 0 ],
1397 						enclosed,
1398 						selected;
1399 
1400 					// Check first any enclosed element, e.g. <ul>[<li><a href="#">item</a></li>]</ul>
1401 					for ( var i = 2; i && !( ( enclosed = range.getEnclosedNode() )
1402 						&& ( enclosed.type == CKEDITOR.NODE_ELEMENT )
1403 						&& styleObjectElements[ enclosed.getName() ]
1404 						&& ( selected = enclosed ) ); i-- )
1405 					{
1406 						// Then check any deep wrapped element, e.g. [<b><i><img /></i></b>]
1407 						range.shrink( CKEDITOR.SHRINK_ELEMENT );
1408 					}
1409 
1410 					return  selected.$;
1411 				});
1412 
1413 			return cache.selectedElement = ( node ? new CKEDITOR.dom.element( node ) : null );
1414 		},
1415 
1416 		/**
1417 		 * Retrieves the text contained within the range. An empty string is returned for non-text selection.
1418 		 * @returns {String} A string of text within the current selection.
1419 		 * @since 3.6.1
1420 		 * @example
1421 		 * var text = editor.getSelection().<strong>getSelectedText()</strong>;
1422 		 * alert( text );
1423 		 */
1424 		getSelectedText : function()
1425 		{
1426 			var cache = this._.cache;
1427 			if ( cache.selectedText !== undefined )
1428 				return cache.selectedText;
1429 
1430 			var text = '',
1431 				nativeSel = this.getNative();
1432 			if ( this.getType() == CKEDITOR.SELECTION_TEXT )
1433 				text = isMSSelection ?
1434 				   nativeSel.type == 'Control' ? '' :
1435 				   nativeSel.createRange().text :
1436 				   nativeSel.toString();
1437 
1438 			return ( cache.selectedText = text );
1439 		},
1440 
1441 		/**
1442 		 * Locks the selection made in the editor in order to make it possible to
1443 		 * manipulate it without browser interference. A locked selection is
1444 		 * cached and remains unchanged until it is released with the <code>#unlock</code>
1445 		 * method.
1446 		 * @example
1447 		 * editor.getSelection().<strong>lock()</strong>;
1448 		 */
1449 		lock : function()
1450 		{
1451 			// Call all cacheable function.
1452 			this.getRanges();
1453 			this.getStartElement();
1454 			this.getSelectedElement();
1455 			this.getSelectedText();
1456 
1457 			// The native selection is not available when locked.
1458 			this._.cache.nativeSel = {};
1459 
1460 			this.isLocked = 1;
1461 
1462 			// Save this selection inside the DOM document.
1463 			this.document.setCustomData( 'cke_locked_selection', this );
1464 		},
1465 
1466 		/**
1467 		 * Unlocks the selection made in the editor and locked with the <code>#lock</code> method.
1468 		 * An unlocked selection is no longer cached and can be changed.
1469 		 * @param {Boolean} [restore] If set to <code>true</code>, the selection is restored back to the selection saved earlier by using the <code>#lock</code> method.
1470 		 * @example
1471 		 * editor.getSelection().<strong>unlock()</strong>;
1472 		 */
1473 		unlock : function( restore )
1474 		{
1475 			var doc = this.document,
1476 				lockedSelection = doc.getCustomData( 'cke_locked_selection' );
1477 
1478 			if ( lockedSelection )
1479 			{
1480 				doc.setCustomData( 'cke_locked_selection', null );
1481 
1482 				if ( restore )
1483 				{
1484 					var selectedElement = lockedSelection.getSelectedElement(),
1485 						ranges = !selectedElement && lockedSelection.getRanges();
1486 
1487 					this.isLocked = 0;
1488 					this.reset();
1489 
1490 					if ( selectedElement )
1491 						this.selectElement( selectedElement );
1492 					else
1493 						this.selectRanges( ranges );
1494 				}
1495 			}
1496 
1497 			if  ( !lockedSelection || !restore )
1498 			{
1499 				this.isLocked = 0;
1500 				this.reset();
1501 			}
1502 		},
1503 
1504 		/**
1505 		 * Clears the selection cache.
1506 		 * @example
1507 		 * editor.getSelection().<strong>reset()</strong>;
1508 		 */
1509 		reset : function()
1510 		{
1511 			this._.cache = {};
1512 		},
1513 
1514 		/**
1515 		 * Makes the current selection of type <code>{@link CKEDITOR.SELECTION_ELEMENT}</code> by enclosing the specified element.
1516 		 * @param {CKEDITOR.dom.element} element The element to enclose in the selection.
1517 		 * @example
1518 		 * var element = editor.document.getById( 'sampleElement' );
1519 		 * editor.getSelection.<strong>selectElement( element )</strong>;
1520 		 */
1521 		selectElement : function( element )
1522 		{
1523 			if ( this.isLocked )
1524 			{
1525 				var range = new CKEDITOR.dom.range( this.document );
1526 				range.setStartBefore( element );
1527 				range.setEndAfter( element );
1528 
1529 				this._.cache.selectedElement = element;
1530 				this._.cache.startElement = element;
1531 				this._.cache.ranges = new CKEDITOR.dom.rangeList( range );
1532 				this._.cache.type = CKEDITOR.SELECTION_ELEMENT;
1533 
1534 				return;
1535 			}
1536 
1537 			range = new CKEDITOR.dom.range( element.getDocument() );
1538 			range.setStartBefore( element );
1539 			range.setEndAfter( element );
1540 			range.select();
1541 
1542 			this.document.fire( 'selectionchange' );
1543 			this.reset();
1544 
1545 		},
1546 
1547 		/**
1548 		 *  Clears the original selection and adds the specified ranges
1549 		 * to the document selection.
1550 		 * @param {Array} ranges An array of <code>{@link CKEDITOR.dom.range}</code> instances representing ranges to be added to the document.
1551 		 * @example
1552 		 * var ranges = new CKEDITOR.dom.range( editor.document );
1553 		 * editor.getSelection().<strong>selectRanges( [ ranges ] )</strong>;
1554 		 */
1555 		selectRanges : function( ranges )
1556 		{
1557 			if ( this.isLocked )
1558 			{
1559 				this._.cache.selectedElement = null;
1560 				this._.cache.startElement = ranges[ 0 ] && ranges[ 0 ].getTouchedStartNode();
1561 				this._.cache.ranges = new CKEDITOR.dom.rangeList( ranges );
1562 				this._.cache.type = CKEDITOR.SELECTION_TEXT;
1563 
1564 				return;
1565 			}
1566 
1567 			if ( isMSSelection )
1568 			{
1569 				if ( ranges.length > 1 )
1570 				{
1571 					// IE doesn't accept multiple ranges selection, so we join all into one.
1572 					var last = ranges[ ranges.length -1 ] ;
1573 					ranges[ 0 ].setEnd( last.endContainer, last.endOffset );
1574 					ranges.length = 1;
1575 				}
1576 
1577 				if ( ranges[ 0 ] )
1578 					ranges[ 0 ].select();
1579 
1580 				this.reset();
1581 			}
1582 			else
1583 			{
1584 				var sel = this.getNative();
1585 
1586 				// getNative() returns null if iframe is "display:none" in FF. (#6577)
1587 				if ( !sel )
1588 					return;
1589 
1590 				// Opera: The above hack work around a *visually wrong* text selection that
1591 				// happens in certain situation. (#6874)
1592 				if ( CKEDITOR.env.opera )
1593 					this.document.$.execCommand( 'SelectAll', false );
1594 
1595 				if ( ranges.length )
1596 				{
1597 					sel.removeAllRanges();
1598 					// Remove any existing filling char first.
1599 					CKEDITOR.env.webkit && removeFillingChar( this.document );
1600 				}
1601 
1602 				for ( var i = 0 ; i < ranges.length ; i++ )
1603 				{
1604 					// Joining sequential ranges introduced by
1605 					// readonly elements protection.
1606 					if ( i < ranges.length -1 )
1607 					{
1608 						var left = ranges[ i ], right = ranges[ i +1 ],
1609 								between = left.clone();
1610 						between.setStart( left.endContainer, left.endOffset );
1611 						between.setEnd( right.startContainer, right.startOffset );
1612 
1613 						// Don't confused by Firefox adjancent multi-ranges
1614 						// introduced by table cells selection.
1615 						if ( !between.collapsed )
1616 						{
1617 							between.shrink( CKEDITOR.NODE_ELEMENT, true );
1618 							var ancestor = between.getCommonAncestor(),
1619 								enclosed = between.getEnclosedNode();
1620 
1621 							// The following cases has to be considered:
1622 							// 1. <span contenteditable="false">[placeholder]</span>
1623 							// 2. <input contenteditable="false"  type="radio"/> (#6621)
1624 							if ( ancestor.isReadOnly() || enclosed && enclosed.isReadOnly() )
1625 							{
1626 								right.setStart( left.startContainer, left.startOffset );
1627 								ranges.splice( i--, 1 );
1628 								continue;
1629 							}
1630 						}
1631 					}
1632 
1633 					var range = ranges[ i ];
1634 					var nativeRange = this.document.$.createRange();
1635 					var startContainer = range.startContainer;
1636 
1637 					// In FF2, if we have a collapsed range, inside an empty
1638 					// element, we must add something to it otherwise the caret
1639 					// will not be visible.
1640 					// In Opera instead, the selection will be moved out of the
1641 					// element. (#4657)
1642 					if ( range.collapsed &&
1643 						( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) ) &&
1644 						startContainer.type == CKEDITOR.NODE_ELEMENT &&
1645 						!startContainer.getChildCount() )
1646 					{
1647 						startContainer.appendText( '' );
1648 					}
1649 
1650 					if ( range.collapsed
1651 							&& CKEDITOR.env.webkit
1652 							&& rangeRequiresFix( range ) )
1653 					{
1654 						// Append a zero-width space so WebKit will not try to
1655 						// move the selection by itself (#1272).
1656 						var fillingChar = createFillingChar( this.document );
1657 						range.insertNode( fillingChar ) ;
1658 
1659 						var next = fillingChar.getNext();
1660 
1661 						// If the filling char is followed by a <br>, whithout
1662 						// having something before it, it'll not blink.
1663 						// Let's remove it in this case.
1664 						if ( next && !fillingChar.getPrevious() && next.type == CKEDITOR.NODE_ELEMENT && next.getName() == 'br' )
1665 						{
1666 							removeFillingChar( this.document );
1667 							range.moveToPosition( next, CKEDITOR.POSITION_BEFORE_START );
1668 						}
1669 						else
1670 							range.moveToPosition( fillingChar, CKEDITOR.POSITION_AFTER_END );
1671 					}
1672 
1673 					nativeRange.setStart( range.startContainer.$, range.startOffset );
1674 
1675 					try
1676 					{
1677 						nativeRange.setEnd( range.endContainer.$, range.endOffset );
1678 					}
1679 					catch ( e )
1680 					{
1681 						// There is a bug in Firefox implementation (it would be too easy
1682 						// otherwise). The new start can't be after the end (W3C says it can).
1683 						// So, let's create a new range and collapse it to the desired point.
1684 						if ( e.toString().indexOf( 'NS_ERROR_ILLEGAL_VALUE' ) >= 0 )
1685 						{
1686 							range.collapse( 1 );
1687 							nativeRange.setEnd( range.endContainer.$, range.endOffset );
1688 						}
1689 						else
1690 							throw e;
1691 					}
1692 
1693 					// Select the range.
1694 					sel.addRange( nativeRange );
1695 				}
1696 
1697 				// Don't miss selection change event for non-IEs.
1698 				this.document.fire( 'selectionchange' );
1699 				this.reset();
1700 			}
1701 		},
1702 
1703 		/**
1704 		 *  Creates a bookmark for each range of this selection (from <code>#getRanges</code>)
1705 		 * by calling the <code>{@link CKEDITOR.dom.range.prototype.createBookmark}</code> method,
1706 		 * with extra care taken to avoid interference among those ranges. The arguments
1707 		 * received are the same as with the underlying range method.
1708 		 * @returns {Array} Array of bookmarks for each range.
1709 		 * @example
1710 		 * var bookmarks = editor.getSelection().<strong>createBookmarks()</strong>;
1711 		 */
1712 		createBookmarks : function( serializable )
1713 		{
1714 			return this.getRanges().createBookmarks( serializable );
1715 		},
1716 
1717 		/**
1718 		 *  Creates a bookmark for each range of this selection (from <code>#getRanges</code>)
1719 		 * by calling the <code>{@link CKEDITOR.dom.range.prototype.createBookmark2}</code> method,
1720 		 * with extra care taken to avoid interference among those ranges. The arguments
1721 		 * received are the same as with the underlying range method.
1722 		 * @returns {Array} Array of bookmarks for each range.
1723 		 * @example
1724 		 * var bookmarks = editor.getSelection().<strong>createBookmarks2()</strong>;
1725 		 */
1726 		createBookmarks2 : function( normalized )
1727 		{
1728 			return this.getRanges().createBookmarks2( normalized );
1729 		},
1730 
1731 		/**
1732 		 * Selects the virtual ranges denoted by the bookmarks by calling <code>#selectRanges</code>.
1733 		 * @param {Array} bookmarks The bookmarks representing ranges to be selected.
1734 		 * @returns {CKEDITOR.dom.selection} This selection object, after the ranges were selected.
1735 		 * @example
1736 		 * var bookmarks = editor.getSelection().createBookmarks();
1737 		 * editor.getSelection().<strong>selectBookmarks( bookmarks )</strong>;
1738 		 */
1739 		selectBookmarks : function( bookmarks )
1740 		{
1741 			var ranges = [];
1742 			for ( var i = 0 ; i < bookmarks.length ; i++ )
1743 			{
1744 				var range = new CKEDITOR.dom.range( this.document );
1745 				range.moveToBookmark( bookmarks[i] );
1746 				ranges.push( range );
1747 			}
1748 			this.selectRanges( ranges );
1749 			return this;
1750 		},
1751 
1752 		/**
1753 		 * Retrieves the common ancestor node of the first range and the last range.
1754 		 * @returns {CKEDITOR.dom.element} The common ancestor of the selection.
1755 		 * @example
1756 		 * var ancestor = editor.getSelection().<strong>getCommonAncestor()</strong>;
1757 		 */
1758 		getCommonAncestor : function()
1759 		{
1760 			var ranges = this.getRanges(),
1761 				startNode = ranges[ 0 ].startContainer,
1762 				endNode = ranges[ ranges.length - 1 ].endContainer;
1763 			return startNode.getCommonAncestor( endNode );
1764 		},
1765 
1766 		/**
1767 		 * Moves the scrollbar to the starting position of the current selection.
1768 		 * @example
1769 		 * editor.getSelection().<strong>scrollIntoView()</strong>;
1770 		 */
1771 		scrollIntoView : function()
1772 		{
1773 			// If we have split the block, adds a temporary span at the
1774 			// range position and scroll relatively to it.
1775 			var start = this.getStartElement();
1776 			start.scrollIntoView();
1777 		}
1778 	};
1779 
1780 	var notWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),
1781 			isVisible = CKEDITOR.dom.walker.invisible( 1 ),
1782 			fillerTextRegex = /\ufeff|\u00a0/,
1783 			nonCells = { table:1,tbody:1,tr:1 };
1784 
1785 	CKEDITOR.dom.range.prototype.select =
1786 			isMSSelection ?
1787 			// V2
1788 			function( forceExpand )
1789 			{
1790 				var collapsed = this.collapsed,
1791 					isStartMarkerAlone, dummySpan, ieRange;
1792 
1793 				// Try to make a object selection.
1794 				var selected = this.getEnclosedNode();
1795 				if ( selected )
1796 				{
1797 					try
1798 					{
1799 						ieRange = this.document.$.body.createControlRange();
1800 						ieRange.addElement( selected.$ );
1801 						ieRange.select();
1802 						return;
1803 					}
1804 					catch( er ) {}
1805 				}
1806 
1807 				// IE doesn't support selecting the entire table row/cell, move the selection into cells, e.g.
1808 				// <table><tbody><tr>[<td>cell</b></td>... => <table><tbody><tr><td>[cell</td>...
1809 				if ( this.startContainer.type == CKEDITOR.NODE_ELEMENT && this.startContainer.getName() in nonCells
1810 					|| this.endContainer.type == CKEDITOR.NODE_ELEMENT && this.endContainer.getName() in nonCells )
1811 				{
1812 					this.shrink( CKEDITOR.NODE_ELEMENT, true );
1813 				}
1814 
1815 				var bookmark = this.createBookmark();
1816 
1817 				// Create marker tags for the start and end boundaries.
1818 				var startNode = bookmark.startNode;
1819 
1820 				var endNode;
1821 				if ( !collapsed )
1822 					endNode = bookmark.endNode;
1823 
1824 				// Create the main range which will be used for the selection.
1825 				ieRange = this.document.$.body.createTextRange();
1826 
1827 				// Position the range at the start boundary.
1828 				ieRange.moveToElementText( startNode.$ );
1829 				ieRange.moveStart( 'character', 1 );
1830 
1831 				if ( endNode )
1832 				{
1833 					// Create a tool range for the end.
1834 					var ieRangeEnd = this.document.$.body.createTextRange();
1835 
1836 					// Position the tool range at the end.
1837 					ieRangeEnd.moveToElementText( endNode.$ );
1838 
1839 					// Move the end boundary of the main range to match the tool range.
1840 					ieRange.setEndPoint( 'EndToEnd', ieRangeEnd );
1841 					ieRange.moveEnd( 'character', -1 );
1842 				}
1843 				else
1844 				{
1845 					// The isStartMarkerAlone logic comes from V2. It guarantees that the lines
1846 					// will expand and that the cursor will be blinking on the right place.
1847 					// Actually, we are using this flag just to avoid using this hack in all
1848 					// situations, but just on those needed.
1849 					var next = startNode.getNext( notWhitespaces );
1850 					isStartMarkerAlone = ( !( next && next.getText && next.getText().match( fillerTextRegex ) )     // already a filler there?
1851 										  && ( forceExpand || !startNode.hasPrevious() || ( startNode.getPrevious().is && startNode.getPrevious().is( 'br' ) ) ) );
1852 
1853 					// Append a temporary <span></span> before the selection.
1854 					// This is needed to avoid IE destroying selections inside empty
1855 					// inline elements, like <b></b> (#253).
1856 					// It is also needed when placing the selection right after an inline
1857 					// element to avoid the selection moving inside of it.
1858 					dummySpan = this.document.createElement( 'span' );
1859 					dummySpan.setHtml( '' );	// Zero Width No-Break Space (U+FEFF). See #1359.
1860 					dummySpan.insertBefore( startNode );
1861 
1862 					if ( isStartMarkerAlone )
1863 					{
1864 						// To expand empty blocks or line spaces after <br>, we need
1865 						// instead to have any char, which will be later deleted using the
1866 						// selection.
1867 						// \ufeff = Zero Width No-Break Space (U+FEFF). (#1359)
1868 						this.document.createText( '\ufeff' ).insertBefore( startNode );
1869 					}
1870 				}
1871 
1872 				// Remove the markers (reset the position, because of the changes in the DOM tree).
1873 				this.setStartBefore( startNode );
1874 				startNode.remove();
1875 
1876 				if ( collapsed )
1877 				{
1878 					if ( isStartMarkerAlone )
1879 					{
1880 						// Move the selection start to include the temporary \ufeff.
1881 						ieRange.moveStart( 'character', -1 );
1882 
1883 						ieRange.select();
1884 
1885 						// Remove our temporary stuff.
1886 						this.document.$.selection.clear();
1887 					}
1888 					else
1889 						ieRange.select();
1890 
1891 					this.moveToPosition( dummySpan, CKEDITOR.POSITION_BEFORE_START );
1892 					dummySpan.remove();
1893 				}
1894 				else
1895 				{
1896 					this.setEndBefore( endNode );
1897 					endNode.remove();
1898 					ieRange.select();
1899 				}
1900 
1901 				this.document.fire( 'selectionchange' );
1902 			}
1903 		:
1904 			function()
1905 			{
1906 				this.document.getSelection().selectRanges( [ this ] );
1907 			};
1908 } )();
1909