1 /*
  2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5
  6 (function()
  7 {
  8 	// #### checkSelectionChange : START
  9
 10 	// The selection change check basically saves the element parent tree of
 11 	// the current node and check it on successive requests. If there is any
 12 	// change on the tree, then the selectionChange event gets fired.
 13 	var checkSelectionChange = function()
 14 	{
 15 		// In IE, the "selectionchange" event may still get thrown when
 16 		// releasing the WYSIWYG mode, so we need to check it first.
 17 		var sel = this.getSelection();
 18 		if ( !sel )
 19 			return;
 20
 21 		var firstElement = sel.getStartElement();
 22 		var currentPath = new CKEDITOR.dom.elementPath( firstElement );
 23
 24 		if ( !currentPath.compare( this._.selectionPreviousPath ) )
 25 		{
 26 			this._.selectionPreviousPath = currentPath;
 27 			this.fire( 'selectionChange', { selection : sel, path : currentPath, element : firstElement } );
 28 		}
 29 	};
 30
 31 	var checkSelectionChangeTimer;
 32 	var checkSelectionChangeTimeoutPending;
 33 	var checkSelectionChangeTimeout = function()
 34 	{
 35 		// Firing the "OnSelectionChange" event on every key press started to
 36 		// be too slow. This function guarantees that there will be at least
 37 		// 200ms delay between selection checks.
 38
 39 		checkSelectionChangeTimeoutPending = true;
 40
 41 		if ( checkSelectionChangeTimer )
 42 			return;
 43
 44 		checkSelectionChangeTimeoutExec.call( this );
 45
 46 		checkSelectionChangeTimer = CKEDITOR.tools.setTimeout( checkSelectionChangeTimeoutExec, 200, this );
 47 	};
 48
 49 	var checkSelectionChangeTimeoutExec = function()
 50 	{
 51 		checkSelectionChangeTimer = null;
 52
 53 		if ( checkSelectionChangeTimeoutPending )
 54 		{
 55 			// Call this with a timeout so the browser properly moves the
 56 			// selection after the mouseup. It happened that the selection was
 57 			// being moved after the mouseup when clicking inside selected text
 58 			// with Firefox.
 59 			CKEDITOR.tools.setTimeout( checkSelectionChange, 0, this );
 60
 61 			checkSelectionChangeTimeoutPending = false;
 62 		}
 63 	};
 64
 65 	// #### checkSelectionChange : END
 66
 67 	var selectAllCmd =
 68 	{
 69 		exec : function( editor )
 70 		{
 71 			switch ( editor.mode )
 72 			{
 73 				case 'wysiwyg' :
 74 					editor.document.$.execCommand( 'SelectAll', false, null );
 75 					break;
 76 				case 'source' :
 77 					// TODO
 78 			}
 79 		}
 80 	};
 81
 82 	CKEDITOR.plugins.add( 'selection',
 83 	{
 84 		init : function( editor )
 85 		{
 86 			editor.on( 'contentDom', function()
 87 				{
 88 					if ( CKEDITOR.env.ie )
 89 					{
 90 						// IE is the only to provide the "selectionchange"
 91 						// event.
 92 						editor.document.on( 'selectionchange', checkSelectionChangeTimeout, editor );
 93 					}
 94 					else
 95 					{
 96 						// In other browsers, we make the selection change
 97 						// check based on other events, like clicks or keys
 98 						// press.
 99
100 						editor.document.on( 'mouseup', checkSelectionChangeTimeout, editor );
101 						editor.document.on( 'keyup', checkSelectionChangeTimeout, editor );
102 					}
103 				});
104
105 			editor.addCommand( 'selectAll', selectAllCmd );
106 			editor.ui.addButton( 'SelectAll',
107 				{
108 					label : editor.lang.selectAll,
109 					command : 'selectAll'
110 				});
111
112 			editor.selectionChange = checkSelectionChangeTimeout;
113 		}
114 	});
115
116 	/**
117 	 * Gets the current selection from the editing area when in WYSIWYG mode.
118 	 * @returns {CKEDITOR.dom.selection} A selection object or null if not on
119 	 *		WYSIWYG mode or no selection is available.
120 	 * @example
121 	 * var selection = CKEDITOR.instances.editor1.<b>getSelection()</b>;
122 	 * alert( selection.getType() );
123 	 */
124 	CKEDITOR.editor.prototype.getSelection = function()
125 	{
126 		var retval = this.document ? this.document.getSelection() : null;
127
128 		/**
129 		 * IE BUG: The selection's document may be a different document than the
130 		 * editor document. Return null if that's the case.
131 		 */
132 		if ( retval && CKEDITOR.env.ie )
133 		{
134 			var range = retval.getNative().createRange();
135 			if ( !range )
136 				return null;
137 			else if ( range.item )
138 				return range.item(0).ownerDocument == this.document.$ ? retval : null;
139 			else
140 				return range.parentElement().ownerDocument == this.document.$ ? retval : null;
141 		}
142
143 		return retval;
144 	};
145
146 	CKEDITOR.editor.prototype.forceNextSelectionCheck = function()
147 	{
148 		delete this._.selectionPreviousPath;
149 	};
150
151 	/**
152 	 * Gets the current selection from the document.
153 	 * @returns {CKEDITOR.dom.selection} A selection object.
154 	 * @example
155 	 * var selection = CKEDITOR.instances.editor1.document.<b>getSelection()</b>;
156 	 * alert( selection.getType() );
157 	 */
158 	CKEDITOR.dom.document.prototype.getSelection = function()
159 	{
160 		return new CKEDITOR.dom.selection( this );
161 	};
162
163 	/**
164 	 * No selection.
165 	 * @constant
166 	 * @example
167 	 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_NONE )
168 	 *     alert( 'Nothing is selected' );
169 	 */
170 	CKEDITOR.SELECTION_NONE		= 1;
171
172 	/**
173 	 * Text or collapsed selection.
174 	 * @constant
175 	 * @example
176 	 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_TEXT )
177 	 *     alert( 'Text is selected' );
178 	 */
179 	CKEDITOR.SELECTION_TEXT		= 2;
180
181 	/**
182 	 * Element selection.
183 	 * @constant
184 	 * @example
185 	 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_ELEMENT )
186 	 *     alert( 'An element is selected' );
187 	 */
188 	CKEDITOR.SELECTION_ELEMENT	= 3;
189
190 	/**
191 	 * Manipulates the selection in a DOM document.
192 	 * @constructor
193 	 * @example
194 	 */
195 	CKEDITOR.dom.selection = function( document )
196 	{
197 		this.document = document;
198 		this._ =
199 		{
200 			cache : {}
201 		};
202 	};
203
204 	var styleObjectElements = { img:1,hr:1,li:1,table:1,tr:1,td:1,embed:1,object:1,ol:1,ul:1 };
205
206 	CKEDITOR.dom.selection.prototype =
207 	{
208 		/**
209 		 * Gets the native selection object from the browser.
210 		 * @function
211 		 * @returns {Object} The native selection object.
212 		 * @example
213 		 * var selection = editor.getSelection().<b>getNative()</b>;
214 		 */
215 		getNative :
216 			CKEDITOR.env.ie ?
217 				function()
218 				{
219 					return this._.cache.nativeSel || ( this._.cache.nativeSel = this.document.$.selection );
220 				}
221 			:
222 				function()
223 				{
224 					return this._.cache.nativeSel || ( this._.cache.nativeSel = this.document.getWindow().$.getSelection() );
225 				},
226
227 		/**
228 		 * Gets the type of the current selection. The following values are
229 		 * available:
230 		 * <ul>
231 		 *		<li>{@link CKEDITOR.SELECTION_NONE} (1): No selection.</li>
232 		 *		<li>{@link CKEDITOR.SELECTION_TEXT} (2): Text is selected or
233 		 *			collapsed selection.</li>
234 		 *		<li>{@link CKEDITOR.SELECTION_ELEMENT} (3): A element
235 		 *			selection.</li>
236 		 * </ul>
237 		 * @function
238 		 * @returns {Number} One of the following constant values:
239 		 *		{@link CKEDITOR.SELECTION_NONE}, {@link CKEDITOR.SELECTION_TEXT} or
240 		 *		{@link CKEDITOR.SELECTION_ELEMENT}.
241 		 * @example
242 		 * if ( editor.getSelection().<b>getType()</b> == CKEDITOR.SELECTION_TEXT )
243 		 *     alert( 'Text is selected' );
244 		 */
245 		getType :
246 			CKEDITOR.env.ie ?
247 				function()
248 				{
249 					if ( this._.cache.type )
250 						return this._.cache.type;
251
252 					var type = CKEDITOR.SELECTION_NONE;
253
254 					try
255 					{
256 						var sel = this.getNative(),
257 							ieType = sel.type;
258
259 						if ( ieType == 'Text' )
260 							type = CKEDITOR.SELECTION_TEXT;
261
262 						if ( ieType == 'Control' )
263 							type = CKEDITOR.SELECTION_ELEMENT;
264
265 						// It is possible that we can still get a text range
266 						// object even when type == 'None' is returned by IE.
267 						// So we'd better check the object returned by
268 						// createRange() rather than by looking at the type.
269 						if ( sel.createRange().parentElement )
270 							type = CKEDITOR.SELECTION_TEXT;
271 					}
272 					catch(e) {}
273
274 					return ( this._.cache.type = type );
275 				}
276 			:
277 				function()
278 				{
279 					if ( this._.cache.type )
280 						return this._.cache.type;
281
282 					var type = CKEDITOR.SELECTION_TEXT;
283
284 					var sel = this.getNative();
285
286 					if ( !sel )
287 						type = CKEDITOR.SELECTION_NONE;
288 					else if ( sel.rangeCount == 1 )
289 					{
290 						// Check if the actual selection is a control (IMG,
291 						// TABLE, HR, etc...).
292
293 						var range = sel.getRangeAt(0),
294 							startContainer = range.startContainer;
295
296 						if ( startContainer == range.endContainer
297 							&& startContainer.nodeType == 1
298 							&& ( range.endOffset - range.startOffset ) == 1
299 							&& styleObjectElements[ startContainer.childNodes[ range.startOffset ].nodeName.toLowerCase() ] )
300 						{
301 							type = CKEDITOR.SELECTION_ELEMENT;
302 						}
303 					}
304
305 					return ( this._.cache.type = type );
306 				},
307
308 		getRanges :
309 			CKEDITOR.env.ie ?
310 				( function()
311 				{
312 					// Finds the container and offset for a specific boundary
313 					// of an IE range.
314 					var getBoundaryInformation = function( range, start )
315 					{
316 						// Creates a collapsed range at the requested boundary.
317 						range = range.duplicate();
318 						range.collapse( start );
319
320 						// Gets the element that encloses the range entirely.
321 						var parent = range.parentElement();
322 						var siblings = parent.childNodes;
323
324 						var testRange;
325
326 						for ( var i = 0 ; i < siblings.length ; i++ )
327 						{
328 							var child = siblings[ i ];
329 							if ( child.nodeType == 1 )
330 							{
331 								testRange = range.duplicate();
332
333 								testRange.moveToElementText( child );
334 								testRange.collapse();
335
336 								var comparison = testRange.compareEndPoints( 'StartToStart', range );
337
338 								if ( comparison > 0 )
339 									break;
340 								else if ( comparison === 0 )
341 									return {
342 										container : parent,
343 										offset : i
344 									};
345
346 								testRange = null;
347 							}
348 						}
349
350 						if ( !testRange )
351 						{
352 							testRange = range.duplicate();
353 							testRange.moveToElementText( parent );
354 							testRange.collapse( false );
355 						}
356
357 						testRange.setEndPoint( 'StartToStart', range );
358 						var distance = testRange.text.length;
359
360 						while ( distance > 0 )
361 							distance -= siblings[ --i ].nodeValue.length;
362
363 						if ( distance === 0 )
364 						{
365 							return {
366 								container : parent,
367 								offset : i
368 							};
369 						}
370 						else
371 						{
372 							return {
373 								container : siblings[ i ],
374 								offset : -distance
375 							};
376 						}
377 					};
378
379 					return function()
380 					{
381 						if ( this._.cache.ranges )
382 							return this._.cache.ranges;
383
384 						// IE doesn't have range support (in the W3C way), so we
385 						// need to do some magic to transform selections into
386 						// CKEDITOR.dom.range instances.
387
388 						var sel = this.getNative(),
389 							nativeRange = sel.createRange(),
390 							type = this.getType(),
391 							range;
392
393 						if ( type == CKEDITOR.SELECTION_TEXT )
394 						{
395 							range = new CKEDITOR.dom.range( this.document );
396
397 							var boundaryInfo = getBoundaryInformation( nativeRange, true );
398 							range.setStart( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset );
399
400 							boundaryInfo = getBoundaryInformation( nativeRange );
401 							range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset );
402
403 							return ( this._.cache.ranges = [ range ] );
404 						}
405 						else if ( type == CKEDITOR.SELECTION_ELEMENT )
406 						{
407 							var retval = this._.cache.ranges = [];
408
409 							for ( var i = 0 ; i < nativeRange.length ; i++ )
410 							{
411 								var element = nativeRange.item( i ),
412 									parentElement = element.parentNode,
413 									j = 0;
414
415 								range = new CKEDITOR.dom.range( this.document );
416
417 								for (; j < parentElement.childNodes.length && parentElement.childNodes[j] != element ; j++ )
418 								{ /*jsl:pass*/ }
419
420 								range.setStart( new CKEDITOR.dom.node( parentElement ), j );
421 								range.setEnd( new CKEDITOR.dom.node( parentElement ), j + 1 );
422 								retval.push( range );
423 							}
424
425 							return retval;
426 						}
427
428 						return ( this._.cache.ranges = [] );
429 					};
430 				})()
431 			:
432 				function()
433 				{
434 					if ( this._.cache.ranges )
435 						return this._.cache.ranges;
436
437 					// On browsers implementing the W3C range, we simply
438 					// tranform the native ranges in CKEDITOR.dom.range
439 					// instances.
440
441 					var ranges = [];
442 					var sel = this.getNative();
443
444 					for ( var i = 0 ; i < sel.rangeCount ; i++ )
445 					{
446 						var nativeRange = sel.getRangeAt( i );
447 						var range = new CKEDITOR.dom.range( this.document );
448
449 						range.setStart( new CKEDITOR.dom.node( nativeRange.startContainer ), nativeRange.startOffset );
450 						range.setEnd( new CKEDITOR.dom.node( nativeRange.endContainer ), nativeRange.endOffset );
451 						ranges.push( range );
452 					}
453
454 					return ( this._.cache.ranges = ranges );
455 				},
456
457 		/**
458 		 * Gets the DOM element in which the selection starts.
459 		 * @returns {CKEDITOR.dom.element} The element at the beginning of the
460 		 *		selection.
461 		 * @example
462 		 * var element = editor.getSelection().<b>getStartElement()</b>;
463 		 * alert( element.getName() );
464 		 */
465 		getStartElement : function()
466 		{
467 			var node,
468 				sel = this.getNative();
469
470 			switch ( this.getType() )
471 			{
472 				case CKEDITOR.SELECTION_ELEMENT :
473 					return this.getSelectedElement();
474
475 				case CKEDITOR.SELECTION_TEXT :
476
477 					var range = this.getRanges()[0];
478
479 					if ( range )
480 					{
481 						if ( !range.collapsed )
482 						{
483 							range.optimize();
484
485 							node = range.startContainer;
486
487 							if ( node.type != CKEDITOR.NODE_ELEMENT )
488 								return node.getParent();
489
490 							node = node.getChild( range.startOffset );
491
492 							if ( !node || node.type != CKEDITOR.NODE_ELEMENT )
493 								return range.startContainer;
494
495 							var child = node.getFirst();
496 							while (  child && child.type == CKEDITOR.NODE_ELEMENT )
497 							{
498 								node = child;
499 								child = child.getFirst();
500 							}
501
502 							return node;
503 						}
504 					}
505
506 					if ( CKEDITOR.env.ie )
507 					{
508 						range = sel.createRange();
509 						range.collapse( true );
510
511 						node = range.parentElement();
512 					}
513 					else
514 					{
515 						node = sel.anchorNode;
516
517 						if ( node.nodeType != 1 )
518 							node = node.parentNode;
519 					}
520 			}
521
522 			return ( node ? new CKEDITOR.dom.element( node ) : null );
523 		},
524
525 		/**
526 		 * Gets the current selected element.
527 		 * @returns {CKEDITOR.dom.element} The selected element. Null if no
528 		 *		selection is available or the selection type is not
529 		 *		{@link CKEDITOR.SELECTION_ELEMENT}.
530 		 * @example
531 		 * var element = editor.getSelection().<b>getSelectedElement()</b>;
532 		 * alert( element.getName() );
533 		 */
534 		getSelectedElement : function()
535 		{
536 			var node;
537
538 			if ( this.getType() == CKEDITOR.SELECTION_ELEMENT )
539 			{
540 				var sel = this.getNative();
541
542 				if ( CKEDITOR.env.ie )
543 				{
544 					try
545 					{
546 						node = sel.createRange().item(0);
547 					}
548 					catch(e) {}
549 				}
550 				else
551 				{
552 					var range = sel.getRangeAt( 0 );
553 					node = range.startContainer.childNodes[ range.startOffset ];
554 				}
555 			}
556
557 			return ( node ? new CKEDITOR.dom.element( node ) : null );
558 		},
559
560 		reset : function()
561 		{
562 			this._.cache = {};
563 		},
564
565 		selectElement :
566 			CKEDITOR.env.ie ?
567 				function( element )
568 				{
569 					this.getNative().empty();
570
571 					var range;
572 					try
573 					{
574 						// Try to select the node as a control.
575 						range = this.document.$.body.createControlRange();
576 						range.addElement( element.$ );
577 					}
578 					catch(e)
579 					{
580 						// If failed, select it as a text range.
581 						range = this.document.$.body.createTextRange();
582 						range.moveToElementText( element.$ );
583 					}
584
585 					range.select();
586 				}
587 			:
588 				function( element )
589 				{
590 					// Create the range for the element.
591 					var range = this.document.$.createRange();
592 					range.selectNode( element.$ );
593
594 					// Select the range.
595 					var sel = this.getNative();
596 					sel.removeAllRanges();
597 					sel.addRange( range );
598 				},
599
600 		selectRanges :
601 			CKEDITOR.env.ie ?
602 				function( ranges )
603 				{
604 					// IE doesn't accept multiple ranges selection, so we just
605 					// select the first one.
606 					if ( ranges[ 0 ] )
607 						ranges[ 0 ].select();
608 				}
609 			:
610 				function( ranges )
611 				{
612 					var sel = this.getNative();
613 					sel.removeAllRanges();
614
615 					for ( var i = 0 ; i < ranges.length ; i++ )
616 					{
617 						var range = ranges[ i ];
618 						var nativeRange = this.document.$.createRange();
619 						nativeRange.setStart( range.startContainer.$, range.startOffset );
620 						nativeRange.setEnd( range.endContainer.$, range.endOffset );
621
622 						// Select the range.
623 						sel.addRange( nativeRange );
624 					}
625 				},
626
627 		createBookmarks : function( serializable )
628 		{
629 			var retval = [],
630 				ranges = this.getRanges();
631 			for ( var i = 0 ; i < ranges.length ; i++ )
632 				retval.push( ranges[i].createBookmark( serializable ) );
633 			return retval;
634 		},
635
636 		createBookmarks2 : function( normalized )
637 		{
638 			var bookmarks = [],
639 				ranges = this.getRanges();
640
641 			for ( var i = 0 ; i < ranges.length ; i++ )
642 				bookmarks.push( ranges[i].createBookmark2( normalized ) );
643
644 			return bookmarks;
645 		},
646
647 		selectBookmarks : function( bookmarks )
648 		{
649 			var ranges = [];
650 			for ( var i = 0 ; i < bookmarks.length ; i++ )
651 			{
652 				var range = new CKEDITOR.dom.range( this.document );
653 				range.moveToBookmark( bookmarks[i] );
654 				ranges.push( range );
655 			}
656 			this.selectRanges( ranges );
657 			return this;
658 		}
659 	};
660 })();
661
662 CKEDITOR.dom.range.prototype.select =
663 	CKEDITOR.env.ie ?
664 		// V2
665 		function()
666 		{
667 			var collapsed = this.collapsed;
668 			var isStartMakerAlone;
669 			var dummySpan;
670
671 			var bookmark = this.createBookmark();
672
673 			// Create marker tags for the start and end boundaries.
674 			var startNode = bookmark.startNode;
675
676 			var endNode;
677 			if ( !collapsed )
678 				endNode = bookmark.endNode;
679
680 			// Create the main range which will be used for the selection.
681 			var ieRange = this.document.$.body.createTextRange();
682
683 			// Position the range at the start boundary.
684 			ieRange.moveToElementText( startNode.$ );
685 			ieRange.moveStart( 'character', 1 );
686
687 			if ( endNode )
688 			{
689 				// Create a tool range for the end.
690 				var ieRangeEnd = this.document.$.body.createTextRange();
691
692 				// Position the tool range at the end.
693 				ieRangeEnd.moveToElementText( endNode.$ );
694
695 				// Move the end boundary of the main range to match the tool range.
696 				ieRange.setEndPoint( 'EndToEnd', ieRangeEnd );
697 				ieRange.moveEnd( 'character', -1 );
698 			}
699 			else
700 			{
701 // The isStartMakerAlone logic comes from V2. It guarantees that the lines
702 // will expand and that the cursor will be blinking on the right place.
703 // Actually, we are using this flag just to avoid using this hack in all
704 // situations, but just on those needed.
705
706 // But, in V3, somehow it is not interested on working whe hitting SHIFT+ENTER
707 // inside text. So, let's jsut leave the hack happen always.
708
709 // I'm still leaving the code here just in case. We may find some other IE
710 // weirdness and uncommenting this stuff may be useful.
711
712 //				isStartMakerAlone = ( !startNode.hasPrevious() || ( startNode.getPrevious().is && startNode.getPrevious().is( 'br' ) ) )
713 //					&& !startNode.hasNext();
714
715 				// Append a temporary <span></span> before the selection.
716 				// This is needed to avoid IE destroying selections inside empty
717 				// inline elements, like <b></b> (#253).
718 				// It is also needed when placing the selection right after an inline
719 				// element to avoid the selection moving inside of it.
720 				dummySpan = this.document.createElement( 'span' );
721 				dummySpan.setHtml( '' );	// Zero Width No-Break Space (U+FEFF). See #1359.
722 				dummySpan.insertBefore( startNode );
723
724 //				if ( isStartMakerAlone )
725 //				{
726 					// To expand empty blocks or line spaces after <br>, we need
727 					// instead to have any char, which will be later deleted using the
728 					// selection.
729 					// \ufeff = Zero Width No-Break Space (U+FEFF). (#1359)
730 					this.document.createText( '\ufeff' ).insertBefore( startNode );
731 //				}
732 			}
733
734 			// Remove the markers (reset the position, because of the changes in the DOM tree).
735 			this.setStartBefore( startNode );
736 			startNode.remove();
737
738 			if ( collapsed )
739 			{
740 //				if ( isStartMakerAlone )
741 //				{
742 					// Move the selection start to include the temporary \ufeff.
743 					ieRange.moveStart( 'character', -1 );
744
745 					ieRange.select();
746
747 					// Remove our temporary stuff.
748 					this.document.$.selection.clear();
749 //				}
750 //				else
751 //					ieRange.select();
752
753 				dummySpan.remove();
754 			}
755 			else
756 			{
757 				this.setEndBefore( endNode );
758 				endNode.remove();
759 				ieRange.select();
760 			}
761 		}
762 	:
763 		function()
764 		{
765 			var startContainer = this.startContainer;
766
767 			// If we have a collapsed range, inside an empty element, we must add
768 			// something to it, otherwise the caret will not be visible.
769 			if ( this.collapsed && startContainer.type == CKEDITOR.NODE_ELEMENT && !startContainer.getChildCount() )
770 				startContainer.append( new CKEDITOR.dom.text( '' ) );
771
772 			var nativeRange = this.document.$.createRange();
773 			nativeRange.setStart( startContainer.$, this.startOffset );
774
775 			try
776 			{
777 				nativeRange.setEnd( this.endContainer.$, this.endOffset );
778 			}
779 			catch ( e )
780 			{
781 				// There is a bug in Firefox implementation (it would be too easy
782 				// otherwise). The new start can't be after the end (W3C says it can).
783 				// So, let's create a new range and collapse it to the desired point.
784 				if ( e.toString().indexOf( 'NS_ERROR_ILLEGAL_VALUE' ) >= 0 )
785 				{
786 					this.collapse( true );
787 					nativeRange.setEnd( this.endContainer.$, this.endOffset );
788 				}
789 				else
790 					throw( e );
791 			}
792
793 			var selection = this.document.getSelection().getNative();
794 			selection.removeAllRanges();
795 			selection.addRange( nativeRange );
796 		};
797