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 	/**
709 	 * Manipulates the selection in a DOM document.
710 	 * @constructor
711 	 * @param {CKEDITOR.dom.document} document The DOM document that contains the selection.
712 	 * @example
713 	 * var sel = new <strong>CKEDITOR.dom.selection( CKEDITOR.document )</strong>;
714 	 */
715 	CKEDITOR.dom.selection = function( document )
716 	{
717 		var lockedSelection = document.getCustomData( 'cke_locked_selection' );
718 
719 		if ( lockedSelection )
720 			return lockedSelection;
721 
722 		this.document = document;
723 		this.isLocked = 0;
724 		this._ =
725 		{
726 			cache : {}
727 		};
728 
729 		/**
730 		 * IE BUG: The selection's document may be a different document than the
731 		 * editor document. Return null if that is the case.
732 		 */
733 		if ( CKEDITOR.env.ie )
734 		{
735 			// Avoid breaking because of it. (#8836)
736 			try
737 			{
738 				var range = this.getNative().createRange();
739 				if ( !range ||
740 					 ( range.item && range.item( 0 ).ownerDocument != this.document.$ ) ||
741 					 ( range.parentElement && range.parentElement().ownerDocument != this.document.$ ) )
742 				{
743 					throw 0;
744 				}
745 			}
746 			catch ( e )
747 			{
748 				this.isInvalid = true;
749 			}
750 		}
751 
752 		return this;
753 	};
754 
755 	var styleObjectElements =
756 		{
757 			img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1,
758 			a:1,input:1,form:1,select:1,textarea:1,button:1,fieldset:1,thead:1,tfoot:1
759 		};
760 
761 	CKEDITOR.dom.selection.prototype =
762 	{
763 		/**
764 		 * Gets the native selection object from the browser.
765 		 * @function
766 		 * @returns {Object} The native browser selection object.
767 		 * @example
768 		 * var selection = editor.getSelection().<strong>getNative()</strong>;
769 		 */
770 		getNative :
771 			CKEDITOR.env.ie ?
772 				function()
773 				{
774 					return this._.cache.nativeSel || ( this._.cache.nativeSel = this.document.$.selection );
775 				}
776 			:
777 				function()
778 				{
779 					return this._.cache.nativeSel || ( this._.cache.nativeSel = this.document.getWindow().$.getSelection() );
780 				},
781 
782 		/**
783 		 * Gets the type of the current selection. The following values are
784 		 * available:
785 		 * <ul>
786 		 *		<li><code>{@link CKEDITOR.SELECTION_NONE}</code> (1): No selection.</li>
787 		 *		<li><code>{@link CKEDITOR.SELECTION_TEXT}</code> (2): A text or a collapsed
788 		 *			selection is selected.</li>
789 		 *		<li><code>{@link CKEDITOR.SELECTION_ELEMENT}</code> (3): An element is
790 		 *			selected.</li>
791 		 * </ul>
792 		 * @function
793 		 * @returns {Number} One of the following constant values:
794 		 *		<code>{@link CKEDITOR.SELECTION_NONE}</code>, <code>{@link CKEDITOR.SELECTION_TEXT}</code>, or
795 		 *		<code>{@link CKEDITOR.SELECTION_ELEMENT}</code>.
796 		 * @example
797 		 * if ( editor.getSelection().<strong>getType()</strong> == CKEDITOR.SELECTION_TEXT )
798 		 *     alert( 'A text is selected' );
799 		 */
800 		getType :
801 			CKEDITOR.env.ie ?
802 				function()
803 				{
804 					var cache = this._.cache;
805 					if ( cache.type )
806 						return cache.type;
807 
808 					var type = CKEDITOR.SELECTION_NONE;
809 
810 					try
811 					{
812 						var sel = this.getNative(),
813 							ieType = sel.type;
814 
815 						if ( ieType == 'Text' )
816 							type = CKEDITOR.SELECTION_TEXT;
817 
818 						if ( ieType == 'Control' )
819 							type = CKEDITOR.SELECTION_ELEMENT;
820 
821 						// It is possible that we can still get a text range
822 						// object even when type == 'None' is returned by IE.
823 						// So we'd better check the object returned by
824 						// createRange() rather than by looking at the type.
825 						if ( sel.createRange().parentElement )
826 							type = CKEDITOR.SELECTION_TEXT;
827 					}
828 					catch(e) {}
829 
830 					return ( cache.type = type );
831 				}
832 			:
833 				function()
834 				{
835 					var cache = this._.cache;
836 					if ( cache.type )
837 						return cache.type;
838 
839 					var type = CKEDITOR.SELECTION_TEXT;
840 
841 					var sel = this.getNative();
842 
843 					if ( !sel )
844 						type = CKEDITOR.SELECTION_NONE;
845 					else if ( sel.rangeCount == 1 )
846 					{
847 						// Check if the actual selection is a control (IMG,
848 						// TABLE, HR, etc...).
849 
850 						var range = sel.getRangeAt(0),
851 							startContainer = range.startContainer;
852 
853 						if ( startContainer == range.endContainer
854 							&& startContainer.nodeType == 1
855 							&& ( range.endOffset - range.startOffset ) == 1
856 							&& styleObjectElements[ startContainer.childNodes[ range.startOffset ].nodeName.toLowerCase() ] )
857 						{
858 							type = CKEDITOR.SELECTION_ELEMENT;
859 						}
860 					}
861 
862 					return ( cache.type = type );
863 				},
864 
865 		/**
866 		 * Retrieves the <code>{@link CKEDITOR.dom.range}</code> instances that represent the current selection.
867 		 * Note: Some browsers return multiple ranges even for a continuous selection. Firefox, for example, returns
868 		 * one range for each table cell when one or more table rows are selected.
869 		 * @function
870 		 * @param {Boolean} [onlyEditables] If set to <code>true</code>, this function retrives editable ranges only.
871 		 * @return {Array} Range instances that represent the current selection.
872 		 * @example
873 		 * var ranges = selection.<strong>getRanges()</strong>;
874 		 * alert( ranges.length );
875 		 */
876 		getRanges : (function()
877 		{
878 			var func = CKEDITOR.env.ie ?
879 				( function()
880 				{
881 					function getNodeIndex( node ) { return new CKEDITOR.dom.node( node ).getIndex(); }
882 
883 					// Finds the container and offset for a specific boundary
884 					// of an IE range.
885 					var getBoundaryInformation = function( range, start )
886 					{
887 						// Creates a collapsed range at the requested boundary.
888 						range = range.duplicate();
889 						range.collapse( start );
890 
891 						// Gets the element that encloses the range entirely.
892 						var parent = range.parentElement(),
893 							doc = parent.ownerDocument;
894 
895 						// Empty parent element, e.g. <i>^</i>
896 						if ( !parent.hasChildNodes() )
897 							return  { container : parent, offset : 0 };
898 
899 						var siblings = parent.children,
900 							child,
901 							sibling,
902 							testRange = range.duplicate(),
903 							startIndex = 0,
904 							endIndex = siblings.length - 1,
905 							index = -1,
906 							position,
907 							distance,
908 							container;
909 
910 						// Binary search over all element childs to test the range to see whether
911 						// range is right on the boundary of one element.
912 						while ( startIndex <= endIndex )
913 						{
914 							index = Math.floor( ( startIndex + endIndex ) / 2 );
915 							child = siblings[ index ];
916 							testRange.moveToElementText( child );
917 							position = testRange.compareEndPoints( 'StartToStart', range );
918 
919 							if ( position > 0 )
920 								endIndex = index - 1;
921 							else if ( position < 0 )
922 								startIndex = index + 1;
923 							else
924 							{
925 								// IE9 report wrong measurement with compareEndPoints when range anchors between two BRs.
926 								// e.g. <p>text<br />^<br /></p> (#7433)
927 								if ( CKEDITOR.env.ie9Compat && child.tagName == 'BR' )
928 								{
929 									// "Fall back" to w3c selection.
930 									var sel = doc.defaultView.getSelection();
931 									return { container : sel[ start ? 'anchorNode' : 'focusNode' ],
932 										offset : sel[ start ? 'anchorOffset' : 'focusOffset' ] };
933 								}
934 								else
935 									return { container : parent, offset : getNodeIndex( child ) };
936 							}
937 						}
938 
939 						// All childs are text nodes,
940 						// or to the right hand of test range are all text nodes. (#6992)
941 						if ( index == -1 || index == siblings.length - 1 && position < 0 )
942 						{
943 							// Adapt test range to embrace the entire parent contents.
944 							testRange.moveToElementText( parent );
945 							testRange.setEndPoint( 'StartToStart', range );
946 
947 							// IE report line break as CRLF with range.text but
948 							// only LF with textnode.nodeValue, normalize them to avoid
949 							// breaking character counting logic below. (#3949)
950 							distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length;
951 
952 							siblings = parent.childNodes;
953 
954 							// Actual range anchor right beside test range at the boundary of text node.
955 							if ( !distance )
956 							{
957 								child = siblings[ siblings.length - 1 ];
958 
959 								if ( child.nodeType != CKEDITOR.NODE_TEXT )
960 									return { container : parent, offset : siblings.length };
961 								else
962 									return { container : child, offset : child.nodeValue.length };
963 							}
964 
965 							// Start the measuring until distance overflows, meanwhile count the text nodes.
966 							var i = siblings.length;
967 							while ( distance > 0 && i > 0 )
968 							{
969 								sibling = siblings[ --i ];
970 								if ( sibling.nodeType == CKEDITOR.NODE_TEXT )
971 								{
972 									container = sibling;
973 									distance -= sibling.nodeValue.length;
974 								}
975 							}
976 
977 							return  { container : container, offset : -distance };
978 						}
979 						// Test range was one offset beyond OR behind the anchored text node.
980 						else
981 						{
982 							// Adapt one side of test range to the actual range
983 							// for measuring the offset between them.
984 							testRange.collapse( position > 0 ? true : false );
985 							testRange.setEndPoint( position > 0 ? 'StartToStart' : 'EndToStart', range );
986 
987 							// IE report line break as CRLF with range.text but
988 							// only LF with textnode.nodeValue, normalize them to avoid
989 							// breaking character counting logic below. (#3949)
990 							distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length;
991 
992 							// Actual range anchor right beside test range at the inner boundary of text node.
993 							if ( !distance )
994 								return { container : parent, offset : getNodeIndex( child ) + ( position > 0 ? 0 : 1 ) };
995 
996 							// Start the measuring until distance overflows, meanwhile count the text nodes.
997 							while ( distance > 0 )
998 							{
999 								try
1000 								{
1001 									sibling = child[ position > 0 ? 'previousSibling' : 'nextSibling' ];
1002 									if ( sibling.nodeType == CKEDITOR.NODE_TEXT )
1003 									{
1004 										distance -= sibling.nodeValue.length;
1005 										container = sibling;
1006 									}
1007 									child = sibling;
1008 								}
1009 								// Measurement in IE could be somtimes wrong because of <select> element. (#4611)
1010 								catch( e )
1011 								{
1012 									return { container : parent, offset : getNodeIndex( child ) };
1013 								}
1014 							}
1015 
1016 							return { container : container, offset : position > 0 ? -distance : container.nodeValue.length + distance };
1017 						}
1018 					};
1019 
1020 					return function()
1021 					{
1022 						// IE doesn't have range support (in the W3C way), so we
1023 						// need to do some magic to transform selections into
1024 						// CKEDITOR.dom.range instances.
1025 
1026 						var sel = this.getNative(),
1027 							nativeRange = sel && sel.createRange(),
1028 							type = this.getType(),
1029 							range;
1030 
1031 						if ( !sel )
1032 							return [];
1033 
1034 						if ( type == CKEDITOR.SELECTION_TEXT )
1035 						{
1036 							range = new CKEDITOR.dom.range( this.document );
1037 
1038 							var boundaryInfo = getBoundaryInformation( nativeRange, true );
1039 							range.setStart( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset );
1040 
1041 							boundaryInfo = getBoundaryInformation( nativeRange );
1042 							range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset );
1043 
1044 							// Correct an invalid IE range case on empty list item. (#5850)
1045 							if ( range.endContainer.getPosition( range.startContainer ) & CKEDITOR.POSITION_PRECEDING
1046 									&& range.endOffset <= range.startContainer.getIndex() )
1047 							{
1048 								range.collapse();
1049 							}
1050 
1051 							return [ range ];
1052 						}
1053 						else if ( type == CKEDITOR.SELECTION_ELEMENT )
1054 						{
1055 							var retval = [];
1056 
1057 							for ( var i = 0 ; i < nativeRange.length ; i++ )
1058 							{
1059 								var element = nativeRange.item( i ),
1060 									parentElement = element.parentNode,
1061 									j = 0;
1062 
1063 								range = new CKEDITOR.dom.range( this.document );
1064 
1065 								for (; j < parentElement.childNodes.length && parentElement.childNodes[j] != element ; j++ )
1066 								{ /*jsl:pass*/ }
1067 
1068 								range.setStart( new CKEDITOR.dom.node( parentElement ), j );
1069 								range.setEnd( new CKEDITOR.dom.node( parentElement ), j + 1 );
1070 								retval.push( range );
1071 							}
1072 
1073 							return retval;
1074 						}
1075 
1076 						return [];
1077 					};
1078 				})()
1079 			:
1080 				function()
1081 				{
1082 
1083 					// On browsers implementing the W3C range, we simply
1084 					// tranform the native ranges in CKEDITOR.dom.range
1085 					// instances.
1086 
1087 					var ranges = [],
1088 						range,
1089 						doc = this.document,
1090 						sel = this.getNative();
1091 
1092 					if ( !sel )
1093 						return ranges;
1094 
1095 					// On WebKit, it may happen that we'll have no selection
1096 					// available. We normalize it here by replicating the
1097 					// behavior of other browsers.
1098 					if ( !sel.rangeCount )
1099 					{
1100 						range = new CKEDITOR.dom.range( doc );
1101 						range.moveToElementEditStart( doc.getBody() );
1102 						ranges.push( range );
1103 					}
1104 
1105 					for ( var i = 0 ; i < sel.rangeCount ; i++ )
1106 					{
1107 						var nativeRange = sel.getRangeAt( i );
1108 
1109 						range = new CKEDITOR.dom.range( doc );
1110 
1111 						range.setStart( new CKEDITOR.dom.node( nativeRange.startContainer ), nativeRange.startOffset );
1112 						range.setEnd( new CKEDITOR.dom.node( nativeRange.endContainer ), nativeRange.endOffset );
1113 						ranges.push( range );
1114 					}
1115 					return ranges;
1116 				};
1117 
1118 			return function( onlyEditables )
1119 			{
1120 				var cache = this._.cache;
1121 				if ( cache.ranges && !onlyEditables )
1122 					return cache.ranges;
1123 				else if ( !cache.ranges )
1124 					cache.ranges = new CKEDITOR.dom.rangeList( func.call( this ) );
1125 
1126 				// Split range into multiple by read-only nodes.
1127 				if ( onlyEditables )
1128 				{
1129 					var ranges = cache.ranges;
1130 					for ( var i = 0; i < ranges.length; i++ )
1131 					{
1132 						var range = ranges[ i ];
1133 
1134 						// Drop range spans inside one ready-only node.
1135 						var parent = range.getCommonAncestor();
1136 						if ( parent.isReadOnly() )
1137 							ranges.splice( i, 1 );
1138 
1139 						if ( range.collapsed )
1140 							continue;
1141 
1142 						// Range may start inside a non-editable element,
1143 						// replace the range start after it.
1144 						if ( range.startContainer.isReadOnly() )
1145 						{
1146 							var current = range.startContainer;
1147 							while( current )
1148 							{
1149 								if ( current.is( 'body' ) || !current.isReadOnly() )
1150 									break;
1151 
1152 								if ( current.type == CKEDITOR.NODE_ELEMENT
1153 										&& current.getAttribute( 'contentEditable' ) == 'false' )
1154 									range.setStartAfter( current );
1155 
1156 								current = current.getParent();
1157 							}
1158 						}
1159 
1160 						var startContainer = range.startContainer,
1161 							endContainer = range.endContainer,
1162 							startOffset = range.startOffset,
1163 							endOffset = range.endOffset,
1164 							walkerRange = range.clone();
1165 
1166 						// Enlarge range start/end with text node to avoid walker
1167 						// being DOM destructive, it doesn't interfere our checking
1168 						// of elements below as well.
1169 						if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
1170 						{
1171 							if ( startOffset >= startContainer.getLength() )
1172 								walkerRange.setStartAfter( startContainer );
1173 							else
1174 								walkerRange.setStartBefore( startContainer );
1175 						}
1176 
1177 						if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
1178 						{
1179 							if ( !endOffset )
1180 								walkerRange.setEndBefore( endContainer );
1181 							else
1182 								walkerRange.setEndAfter( endContainer );
1183 						}
1184 
1185 						// Looking for non-editable element inside the range.
1186 						var walker = new CKEDITOR.dom.walker( walkerRange );
1187 						walker.evaluator = function( node )
1188 						{
1189 							if ( node.type == CKEDITOR.NODE_ELEMENT
1190 								&& node.isReadOnly() )
1191 							{
1192 								var newRange = range.clone();
1193 								range.setEndBefore( node );
1194 
1195 								// Drop collapsed range around read-only elements,
1196 								// it make sure the range list empty when selecting
1197 								// only non-editable elements.
1198 								if ( range.collapsed )
1199 									ranges.splice( i--, 1 );
1200 
1201 								// Avoid creating invalid range.
1202 								if ( !( node.getPosition( walkerRange.endContainer ) & CKEDITOR.POSITION_CONTAINS ) )
1203 								{
1204 									newRange.setStartAfter( node );
1205 									if ( !newRange.collapsed )
1206 										ranges.splice( i + 1, 0, newRange );
1207 								}
1208 
1209 								return true;
1210 							}
1211 
1212 							return false;
1213 						};
1214 
1215 						walker.next();
1216 					}
1217 				}
1218 
1219 				return cache.ranges;
1220 			};
1221 		})(),
1222 
1223 		/**
1224 		 * Gets the DOM element in which the selection starts.
1225 		 * @returns {CKEDITOR.dom.element} The element at the beginning of the
1226 		 *		selection.
1227 		 * @example
1228 		 * var element = editor.getSelection().<strong>getStartElement()</strong>;
1229 		 * alert( element.getName() );
1230 		 */
1231 		getStartElement : function()
1232 		{
1233 			var cache = this._.cache;
1234 			if ( cache.startElement !== undefined )
1235 				return cache.startElement;
1236 
1237 			var node,
1238 				sel = this.getNative();
1239 
1240 			switch ( this.getType() )
1241 			{
1242 				case CKEDITOR.SELECTION_ELEMENT :
1243 					return this.getSelectedElement();
1244 
1245 				case CKEDITOR.SELECTION_TEXT :
1246 
1247 					var range = this.getRanges()[0];
1248 
1249 					if ( range )
1250 					{
1251 						if ( !range.collapsed )
1252 						{
1253 							range.optimize();
1254 
1255 							// Decrease the range content to exclude particial
1256 							// selected node on the start which doesn't have
1257 							// visual impact. ( #3231 )
1258 							while ( 1 )
1259 							{
1260 								var startContainer = range.startContainer,
1261 									startOffset = range.startOffset;
1262 								// Limit the fix only to non-block elements.(#3950)
1263 								if ( startOffset == ( startContainer.getChildCount ?
1264 									 startContainer.getChildCount() : startContainer.getLength() )
1265 									 && !startContainer.isBlockBoundary() )
1266 									range.setStartAfter( startContainer );
1267 								else break;
1268 							}
1269 
1270 							node = range.startContainer;
1271 
1272 							if ( node.type != CKEDITOR.NODE_ELEMENT )
1273 								return node.getParent();
1274 
1275 							node = node.getChild( range.startOffset );
1276 
1277 							if ( !node || node.type != CKEDITOR.NODE_ELEMENT )
1278 								node = range.startContainer;
1279 							else
1280 							{
1281 								var child = node.getFirst();
1282 								while (  child && child.type == CKEDITOR.NODE_ELEMENT )
1283 								{
1284 									node = child;
1285 									child = child.getFirst();
1286 								}
1287 							}
1288 						}
1289 						else
1290 						{
1291 							node = range.startContainer;
1292 							if ( node.type != CKEDITOR.NODE_ELEMENT )
1293 								node = node.getParent();
1294 						}
1295 
1296 						node = node.$;
1297 					}
1298 			}
1299 
1300 			return cache.startElement = ( node ? new CKEDITOR.dom.element( node ) : null );
1301 		},
1302 
1303 		/**
1304 		 * Gets the currently selected element.
1305 		 * @returns {CKEDITOR.dom.element} The selected element. Null if no
1306 		 *		selection is available or the selection type is not
1307 		 *		<code>{@link CKEDITOR.SELECTION_ELEMENT}</code>.
1308 		 * @example
1309 		 * var element = editor.getSelection().<strong>getSelectedElement()</strong>;
1310 		 * alert( element.getName() );
1311 		 */
1312 		getSelectedElement : function()
1313 		{
1314 			var cache = this._.cache;
1315 			if ( cache.selectedElement !== undefined )
1316 				return cache.selectedElement;
1317 
1318 			var self = this;
1319 
1320 			var node = CKEDITOR.tools.tryThese(
1321 				// Is it native IE control type selection?
1322 				function()
1323 				{
1324 					return self.getNative().createRange().item( 0 );
1325 				},
1326 				// If a table or list is fully selected.
1327 				function()
1328 				{
1329 					var root,
1330 						retval,
1331 						range  = self.getRanges()[ 0 ],
1332 						ancestor = range.getCommonAncestor( 1, 1 ),
1333 						tags = { table:1,ul:1,ol:1,dl:1 };
1334 
1335 					for ( var t in tags )
1336 					{
1337 						if ( ( root = ancestor.getAscendant( t, 1 ) ) )
1338 							break;
1339 					}
1340 
1341 					if ( root )
1342 					{
1343 						// Enlarging the start boundary.
1344 						var testRange = new CKEDITOR.dom.range( this.document );
1345 						testRange.setStartAt( root, CKEDITOR.POSITION_AFTER_START );
1346 						testRange.setEnd( range.startContainer, range.startOffset );
1347 
1348 						var enlargeables = CKEDITOR.tools.extend( tags, CKEDITOR.dtd.$listItem, CKEDITOR.dtd.$tableContent ),
1349 							walker = new CKEDITOR.dom.walker( testRange ),
1350 							// Check the range is at the inner boundary of the structural element.
1351 							guard = function( walker, isEnd )
1352 							{
1353 								return function( node, isWalkOut )
1354 								{
1355 									if ( node.type == CKEDITOR.NODE_TEXT && ( !CKEDITOR.tools.trim( node.getText() ) || node.getParent().data( 'cke-bookmark' ) ) )
1356 										return true;
1357 
1358 									var tag;
1359 									if ( node.type == CKEDITOR.NODE_ELEMENT )
1360 									{
1361 										tag = node.getName();
1362 
1363 										// Bypass bogus br at the end of block.
1364 										if ( tag == 'br' && isEnd && node.equals( node.getParent().getBogus() ) )
1365 											return true;
1366 
1367 										if ( isWalkOut && tag in enlargeables || tag in CKEDITOR.dtd.$removeEmpty )
1368 											return true;
1369 									}
1370 
1371 									walker.halted = 1;
1372 									return false;
1373 								};
1374 							};
1375 
1376 						walker.guard = guard( walker );
1377 
1378 						if ( walker.checkBackward() && !walker.halted )
1379 						{
1380 							walker = new CKEDITOR.dom.walker( testRange );
1381 							testRange.setStart( range.endContainer, range.endOffset );
1382 							testRange.setEndAt( root, CKEDITOR.POSITION_BEFORE_END );
1383 							walker.guard = guard( walker, 1 );
1384 							if ( walker.checkForward() && !walker.halted )
1385 								retval = root.$;
1386 						}
1387 					}
1388 
1389 					if ( !retval )
1390 						throw 0;
1391 
1392 					return retval;
1393 				},
1394 				// Figure it out by checking if there's a single enclosed
1395 				// node of the range.
1396 				function()
1397 				{
1398 					var range  = self.getRanges()[ 0 ],
1399 						enclosed,
1400 						selected;
1401 
1402 					// Check first any enclosed element, e.g. <ul>[<li><a href="#">item</a></li>]</ul>
1403 					for ( var i = 2; i && !( ( enclosed = range.getEnclosedNode() )
1404 						&& ( enclosed.type == CKEDITOR.NODE_ELEMENT )
1405 						&& styleObjectElements[ enclosed.getName() ]
1406 						&& ( selected = enclosed ) ); i-- )
1407 					{
1408 						// Then check any deep wrapped element, e.g. [<b><i><img /></i></b>]
1409 						range.shrink( CKEDITOR.SHRINK_ELEMENT );
1410 					}
1411 
1412 					return  selected.$;
1413 				});
1414 
1415 			return cache.selectedElement = ( node ? new CKEDITOR.dom.element( node ) : null );
1416 		},
1417 
1418 		/**
1419 		 * Retrieves the text contained within the range. An empty string is returned for non-text selection.
1420 		 * @returns {String} A string of text within the current selection.
1421 		 * @since 3.6.1
1422 		 * @example
1423 		 * var text = editor.getSelection().<strong>getSelectedText()</strong>;
1424 		 * alert( text );
1425 		 */
1426 		getSelectedText : function()
1427 		{
1428 			var cache = this._.cache;
1429 			if ( cache.selectedText !== undefined )
1430 				return cache.selectedText;
1431 
1432 			var text = '',
1433 				nativeSel = this.getNative();
1434 			if ( this.getType() == CKEDITOR.SELECTION_TEXT )
1435 				text = CKEDITOR.env.ie ? nativeSel.createRange().text : nativeSel.toString();
1436 
1437 			return ( cache.selectedText = text );
1438 		},
1439 
1440 		/**
1441 		 * Locks the selection made in the editor in order to make it possible to
1442 		 * manipulate it without browser interference. A locked selection is
1443 		 * cached and remains unchanged until it is released with the <code>#unlock</code>
1444 		 * method.
1445 		 * @example
1446 		 * editor.getSelection().<strong>lock()</strong>;
1447 		 */
1448 		lock : function()
1449 		{
1450 			// Call all cacheable function.
1451 			this.getRanges();
1452 			this.getStartElement();
1453 			this.getSelectedElement();
1454 			this.getSelectedText();
1455 
1456 			// The native selection is not available when locked.
1457 			this._.cache.nativeSel = {};
1458 
1459 			this.isLocked = 1;
1460 
1461 			// Save this selection inside the DOM document.
1462 			this.document.setCustomData( 'cke_locked_selection', this );
1463 		},
1464 
1465 		/**
1466 		 * Unlocks the selection made in the editor and locked with the <code>#lock</code> method.
1467 		 * An unlocked selection is no longer cached and can be changed.
1468 		 * @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.
1469 		 * @example
1470 		 * editor.getSelection().<strong>unlock()</strong>;
1471 		 */
1472 		unlock : function( restore )
1473 		{
1474 			var doc = this.document,
1475 				lockedSelection = doc.getCustomData( 'cke_locked_selection' );
1476 
1477 			if ( lockedSelection )
1478 			{
1479 				doc.setCustomData( 'cke_locked_selection', null );
1480 
1481 				if ( restore )
1482 				{
1483 					var selectedElement = lockedSelection.getSelectedElement(),
1484 						ranges = !selectedElement && lockedSelection.getRanges();
1485 
1486 					this.isLocked = 0;
1487 					this.reset();
1488 
1489 					if ( selectedElement )
1490 						this.selectElement( selectedElement );
1491 					else
1492 						this.selectRanges( ranges );
1493 				}
1494 			}
1495 
1496 			if  ( !lockedSelection || !restore )
1497 			{
1498 				this.isLocked = 0;
1499 				this.reset();
1500 			}
1501 		},
1502 
1503 		/**
1504 		 * Clears the selection cache.
1505 		 * @example
1506 		 * editor.getSelection().<strong>reset()</strong>;
1507 		 */
1508 		reset : function()
1509 		{
1510 			this._.cache = {};
1511 		},
1512 
1513 		/**
1514 		 * Makes the current selection of type <code>{@link CKEDITOR.SELECTION_ELEMENT}</code> by enclosing the specified element.
1515 		 * @param {CKEDITOR.dom.element} element The element to enclose in the selection.
1516 		 * @example
1517 		 * var element = editor.document.getById( 'sampleElement' );
1518 		 * editor.getSelection.<strong>selectElement( element )</strong>;
1519 		 */
1520 		selectElement : function( element )
1521 		{
1522 			if ( this.isLocked )
1523 			{
1524 				var range = new CKEDITOR.dom.range( this.document );
1525 				range.setStartBefore( element );
1526 				range.setEndAfter( element );
1527 
1528 				this._.cache.selectedElement = element;
1529 				this._.cache.startElement = element;
1530 				this._.cache.ranges = new CKEDITOR.dom.rangeList( range );
1531 				this._.cache.type = CKEDITOR.SELECTION_ELEMENT;
1532 
1533 				return;
1534 			}
1535 
1536 			range = new CKEDITOR.dom.range( element.getDocument() );
1537 			range.setStartBefore( element );
1538 			range.setEndAfter( element );
1539 			range.select();
1540 
1541 			this.document.fire( 'selectionchange' );
1542 			this.reset();
1543 
1544 		},
1545 
1546 		/**
1547 		 *  Clears the original selection and adds the specified ranges
1548 		 * to the document selection.
1549 		 * @param {Array} ranges An array of <code>{@link CKEDITOR.dom.range}</code> instances representing ranges to be added to the document.
1550 		 * @example
1551 		 * var ranges = new CKEDITOR.dom.range( editor.document );
1552 		 * editor.getSelection().<strong>selectRanges( [ ranges ] )</strong>;
1553 		 */
1554 		selectRanges : function( ranges )
1555 		{
1556 			if ( this.isLocked )
1557 			{
1558 				this._.cache.selectedElement = null;
1559 				this._.cache.startElement = ranges[ 0 ] && ranges[ 0 ].getTouchedStartNode();
1560 				this._.cache.ranges = new CKEDITOR.dom.rangeList( ranges );
1561 				this._.cache.type = CKEDITOR.SELECTION_TEXT;
1562 
1563 				return;
1564 			}
1565 
1566 			if ( CKEDITOR.env.ie )
1567 			{
1568 				if ( ranges.length > 1 )
1569 				{
1570 					// IE doesn't accept multiple ranges selection, so we join all into one.
1571 					var last = ranges[ ranges.length -1 ] ;
1572 					ranges[ 0 ].setEnd( last.endContainer, last.endOffset );
1573 					ranges.length = 1;
1574 				}
1575 
1576 				if ( ranges[ 0 ] )
1577 					ranges[ 0 ].select();
1578 
1579 				this.reset();
1580 			}
1581 			else
1582 			{
1583 				var sel = this.getNative();
1584 
1585 				// getNative() returns null if iframe is "display:none" in FF. (#6577)
1586 				if ( !sel )
1587 					return;
1588 
1589 				// Opera: The above hack work around a *visually wrong* text selection that
1590 				// happens in certain situation. (#6874)
1591 				if ( CKEDITOR.env.opera )
1592 					this.document.$.execCommand( 'SelectAll', false );
1593 
1594 				if ( ranges.length )
1595 				{
1596 					sel.removeAllRanges();
1597 					// Remove any existing filling char first.
1598 					CKEDITOR.env.webkit && removeFillingChar( this.document );
1599 				}
1600 
1601 				for ( var i = 0 ; i < ranges.length ; i++ )
1602 				{
1603 					// Joining sequential ranges introduced by
1604 					// readonly elements protection.
1605 					if ( i < ranges.length -1 )
1606 					{
1607 						var left = ranges[ i ], right = ranges[ i +1 ],
1608 								between = left.clone();
1609 						between.setStart( left.endContainer, left.endOffset );
1610 						between.setEnd( right.startContainer, right.startOffset );
1611 
1612 						// Don't confused by Firefox adjancent multi-ranges
1613 						// introduced by table cells selection.
1614 						if ( !between.collapsed )
1615 						{
1616 							between.shrink( CKEDITOR.NODE_ELEMENT, true );
1617 							var ancestor = between.getCommonAncestor(),
1618 								enclosed = between.getEnclosedNode();
1619 
1620 							// The following cases has to be considered:
1621 							// 1. <span contenteditable="false">[placeholder]</span>
1622 							// 2. <input contenteditable="false"  type="radio"/> (#6621)
1623 							if ( ancestor.isReadOnly() || enclosed && enclosed.isReadOnly() )
1624 							{
1625 								right.setStart( left.startContainer, left.startOffset );
1626 								ranges.splice( i--, 1 );
1627 								continue;
1628 							}
1629 						}
1630 					}
1631 
1632 					var range = ranges[ i ];
1633 					var nativeRange = this.document.$.createRange();
1634 					var startContainer = range.startContainer;
1635 
1636 					// In FF2, if we have a collapsed range, inside an empty
1637 					// element, we must add something to it otherwise the caret
1638 					// will not be visible.
1639 					// In Opera instead, the selection will be moved out of the
1640 					// element. (#4657)
1641 					if ( range.collapsed &&
1642 						( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) ) &&
1643 						startContainer.type == CKEDITOR.NODE_ELEMENT &&
1644 						!startContainer.getChildCount() )
1645 					{
1646 						startContainer.appendText( '' );
1647 					}
1648 
1649 					if ( range.collapsed
1650 							&& CKEDITOR.env.webkit
1651 							&& rangeRequiresFix( range ) )
1652 					{
1653 						// Append a zero-width space so WebKit will not try to
1654 						// move the selection by itself (#1272).
1655 						var fillingChar = createFillingChar( this.document );
1656 						range.insertNode( fillingChar ) ;
1657 
1658 						var next = fillingChar.getNext();
1659 
1660 						// If the filling char is followed by a <br>, whithout
1661 						// having something before it, it'll not blink.
1662 						// Let's remove it in this case.
1663 						if ( next && !fillingChar.getPrevious() && next.type == CKEDITOR.NODE_ELEMENT && next.getName() == 'br' )
1664 						{
1665 							removeFillingChar( this.document );
1666 							range.moveToPosition( next, CKEDITOR.POSITION_BEFORE_START );
1667 						}
1668 						else
1669 							range.moveToPosition( fillingChar, CKEDITOR.POSITION_AFTER_END );
1670 					}
1671 
1672 					nativeRange.setStart( range.startContainer.$, range.startOffset );
1673 
1674 					try
1675 					{
1676 						nativeRange.setEnd( range.endContainer.$, range.endOffset );
1677 					}
1678 					catch ( e )
1679 					{
1680 						// There is a bug in Firefox implementation (it would be too easy
1681 						// otherwise). The new start can't be after the end (W3C says it can).
1682 						// So, let's create a new range and collapse it to the desired point.
1683 						if ( e.toString().indexOf( 'NS_ERROR_ILLEGAL_VALUE' ) >= 0 )
1684 						{
1685 							range.collapse( 1 );
1686 							nativeRange.setEnd( range.endContainer.$, range.endOffset );
1687 						}
1688 						else
1689 							throw e;
1690 					}
1691 
1692 					// Select the range.
1693 					sel.addRange( nativeRange );
1694 				}
1695 
1696 				// Don't miss selection change event for non-IEs.
1697 				this.document.fire( 'selectionchange' );
1698 				this.reset();
1699 			}
1700 		},
1701 
1702 		/**
1703 		 *  Creates a bookmark for each range of this selection (from <code>#getRanges</code>)
1704 		 * by calling the <code>{@link CKEDITOR.dom.range.prototype.createBookmark}</code> method,
1705 		 * with extra care taken to avoid interference among those ranges. The arguments
1706 		 * received are the same as with the underlying range method.
1707 		 * @returns {Array} Array of bookmarks for each range.
1708 		 * @example
1709 		 * var bookmarks = editor.getSelection().<strong>createBookmarks()</strong>;
1710 		 */
1711 		createBookmarks : function( serializable )
1712 		{
1713 			return this.getRanges().createBookmarks( serializable );
1714 		},
1715 
1716 		/**
1717 		 *  Creates a bookmark for each range of this selection (from <code>#getRanges</code>)
1718 		 * by calling the <code>{@link CKEDITOR.dom.range.prototype.createBookmark2}</code> method,
1719 		 * with extra care taken to avoid interference among those ranges. The arguments
1720 		 * received are the same as with the underlying range method.
1721 		 * @returns {Array} Array of bookmarks for each range.
1722 		 * @example
1723 		 * var bookmarks = editor.getSelection().<strong>createBookmarks2()</strong>;
1724 		 */
1725 		createBookmarks2 : function( normalized )
1726 		{
1727 			return this.getRanges().createBookmarks2( normalized );
1728 		},
1729 
1730 		/**
1731 		 * Selects the virtual ranges denoted by the bookmarks by calling <code>#selectRanges</code>.
1732 		 * @param {Array} bookmarks The bookmarks representing ranges to be selected.
1733 		 * @returns {CKEDITOR.dom.selection} This selection object, after the ranges were selected.
1734 		 * @example
1735 		 * var bookmarks = editor.getSelection().createBookmarks();
1736 		 * editor.getSelection().<strong>selectBookmarks( bookmarks )</strong>;
1737 		 */
1738 		selectBookmarks : function( bookmarks )
1739 		{
1740 			var ranges = [];
1741 			for ( var i = 0 ; i < bookmarks.length ; i++ )
1742 			{
1743 				var range = new CKEDITOR.dom.range( this.document );
1744 				range.moveToBookmark( bookmarks[i] );
1745 				ranges.push( range );
1746 			}
1747 			this.selectRanges( ranges );
1748 			return this;
1749 		},
1750 
1751 		/**
1752 		 * Retrieves the common ancestor node of the first range and the last range.
1753 		 * @returns {CKEDITOR.dom.element} The common ancestor of the selection.
1754 		 * @example
1755 		 * var ancestor = editor.getSelection().<strong>getCommonAncestor()</strong>;
1756 		 */
1757 		getCommonAncestor : function()
1758 		{
1759 			var ranges = this.getRanges(),
1760 				startNode = ranges[ 0 ].startContainer,
1761 				endNode = ranges[ ranges.length - 1 ].endContainer;
1762 			return startNode.getCommonAncestor( endNode );
1763 		},
1764 
1765 		/**
1766 		 * Moves the scrollbar to the starting position of the current selection.
1767 		 * @example
1768 		 * editor.getSelection().<strong>scrollIntoView()</strong>;
1769 		 */
1770 		scrollIntoView : function()
1771 		{
1772 			// If we have split the block, adds a temporary span at the
1773 			// range position and scroll relatively to it.
1774 			var start = this.getStartElement();
1775 			start.scrollIntoView();
1776 		}
1777 	};
1778 
1779 	var notWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),
1780 			isVisible = CKEDITOR.dom.walker.invisible( 1 ),
1781 			fillerTextRegex = /\ufeff|\u00a0/,
1782 			nonCells = { table:1,tbody:1,tr:1 };
1783 
1784 	CKEDITOR.dom.range.prototype.select =
1785 		CKEDITOR.env.ie ?
1786 			// V2
1787 			function( forceExpand )
1788 			{
1789 				var collapsed = this.collapsed,
1790 					isStartMarkerAlone, dummySpan, ieRange;
1791 
1792 				// Try to make a object selection.
1793 				var selected = this.getEnclosedNode();
1794 				if ( selected )
1795 				{
1796 					try
1797 					{
1798 						ieRange = this.document.$.body.createControlRange();
1799 						ieRange.addElement( selected.$ );
1800 						ieRange.select();
1801 						return;
1802 					}
1803 					catch( er ) {}
1804 				}
1805 
1806 				// IE doesn't support selecting the entire table row/cell, move the selection into cells, e.g.
1807 				// <table><tbody><tr>[<td>cell</b></td>... => <table><tbody><tr><td>[cell</td>...
1808 				if ( this.startContainer.type == CKEDITOR.NODE_ELEMENT && this.startContainer.getName() in nonCells
1809 					|| this.endContainer.type == CKEDITOR.NODE_ELEMENT && this.endContainer.getName() in nonCells )
1810 				{
1811 					this.shrink( CKEDITOR.NODE_ELEMENT, true );
1812 				}
1813 
1814 				var bookmark = this.createBookmark();
1815 
1816 				// Create marker tags for the start and end boundaries.
1817 				var startNode = bookmark.startNode;
1818 
1819 				var endNode;
1820 				if ( !collapsed )
1821 					endNode = bookmark.endNode;
1822 
1823 				// Create the main range which will be used for the selection.
1824 				ieRange = this.document.$.body.createTextRange();
1825 
1826 				// Position the range at the start boundary.
1827 				ieRange.moveToElementText( startNode.$ );
1828 				ieRange.moveStart( 'character', 1 );
1829 
1830 				if ( endNode )
1831 				{
1832 					// Create a tool range for the end.
1833 					var ieRangeEnd = this.document.$.body.createTextRange();
1834 
1835 					// Position the tool range at the end.
1836 					ieRangeEnd.moveToElementText( endNode.$ );
1837 
1838 					// Move the end boundary of the main range to match the tool range.
1839 					ieRange.setEndPoint( 'EndToEnd', ieRangeEnd );
1840 					ieRange.moveEnd( 'character', -1 );
1841 				}
1842 				else
1843 				{
1844 					// The isStartMarkerAlone logic comes from V2. It guarantees that the lines
1845 					// will expand and that the cursor will be blinking on the right place.
1846 					// Actually, we are using this flag just to avoid using this hack in all
1847 					// situations, but just on those needed.
1848 					var next = startNode.getNext( notWhitespaces );
1849 					isStartMarkerAlone = ( !( next && next.getText && next.getText().match( fillerTextRegex ) )     // already a filler there?
1850 										  && ( forceExpand || !startNode.hasPrevious() || ( startNode.getPrevious().is && startNode.getPrevious().is( 'br' ) ) ) );
1851 
1852 					// Append a temporary <span></span> before the selection.
1853 					// This is needed to avoid IE destroying selections inside empty
1854 					// inline elements, like <b></b> (#253).
1855 					// It is also needed when placing the selection right after an inline
1856 					// element to avoid the selection moving inside of it.
1857 					dummySpan = this.document.createElement( 'span' );
1858 					dummySpan.setHtml( '' );	// Zero Width No-Break Space (U+FEFF). See #1359.
1859 					dummySpan.insertBefore( startNode );
1860 
1861 					if ( isStartMarkerAlone )
1862 					{
1863 						// To expand empty blocks or line spaces after <br>, we need
1864 						// instead to have any char, which will be later deleted using the
1865 						// selection.
1866 						// \ufeff = Zero Width No-Break Space (U+FEFF). (#1359)
1867 						this.document.createText( '\ufeff' ).insertBefore( startNode );
1868 					}
1869 				}
1870 
1871 				// Remove the markers (reset the position, because of the changes in the DOM tree).
1872 				this.setStartBefore( startNode );
1873 				startNode.remove();
1874 
1875 				if ( collapsed )
1876 				{
1877 					if ( isStartMarkerAlone )
1878 					{
1879 						// Move the selection start to include the temporary \ufeff.
1880 						ieRange.moveStart( 'character', -1 );
1881 
1882 						ieRange.select();
1883 
1884 						// Remove our temporary stuff.
1885 						this.document.$.selection.clear();
1886 					}
1887 					else
1888 						ieRange.select();
1889 
1890 					this.moveToPosition( dummySpan, CKEDITOR.POSITION_BEFORE_START );
1891 					dummySpan.remove();
1892 				}
1893 				else
1894 				{
1895 					this.setEndBefore( endNode );
1896 					endNode.remove();
1897 					ieRange.select();
1898 				}
1899 
1900 				this.document.fire( 'selectionchange' );
1901 			}
1902 		:
1903 			function()
1904 			{
1905 				this.document.getSelection().selectRanges( [ this ] );
1906 			};
1907 } )();
1908