1 /**
  2  * modified from ckeditor core - selection
  3  * @author yiminghe@gmail.com
  4  */
  5 /*
  6  Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
  7  For licensing, see LICENSE.html or http://ckeditor.com/license
  8  */
  9 KISSY.add("editor/core/selection", function (S) {
 10 
 11     var Editor = S.Editor;
 12 
 13     /**
 14      * selection type enum
 15      * @enum {number}
 16      */
 17     Editor.SELECTION = {
 18         SELECTION_NONE:1,
 19         SELECTION_TEXT:2,
 20         SELECTION_ELEMENT:3
 21 
 22     };
 23     var TRUE = true,
 24         FALSE = false,
 25         NULL = null,
 26         UA = S.UA,
 27         DOM = S.DOM,
 28     //tryThese = Editor.Utils.tryThese,
 29         Node = S.Node,
 30         KES = Editor.SELECTION,
 31         KER = Editor.RANGE,
 32     // ie9 仍然采用老的 range api,发现新的不稳定
 33         OLD_IE = UA['ie'], //!window.getSelection,
 34     //EventTarget = S.EventTarget,
 35         Walker = Editor.Walker,
 36     //ElementPath = Editor.ElementPath,
 37         KERange = Editor.Range;
 38 
 39     /**
 40      * @constructor
 41      * @param document {Document}
 42      */
 43     function KESelection(document) {
 44         var self = this;
 45         self.document = document;
 46         self._ = {
 47             cache:{}
 48         };
 49 
 50         /**
 51          * IE BUG: The selection's document may be a different document than the
 52          * editor document. Return NULL if that's the case.
 53          */
 54         if (OLD_IE) {
 55             try {
 56                 var range = self.getNative().createRange();
 57                 if (!range
 58                     || ( range.item && range.item(0).ownerDocument != document )
 59                     || ( range.parentElement && range.parentElement().ownerDocument != document )) {
 60                     self.isInvalid = TRUE;
 61                 }
 62             }
 63                 // 2012-06-13 发布页 bug
 64                 // 当焦点在一个跨域的 iframe 内,调用该操作抛拒绝访问异常
 65             catch (e) {
 66                 self.isInvalid = TRUE;
 67             }
 68         }
 69     }
 70 
 71     var styleObjectElements = {
 72         img:1, hr:1, li:1, table:1, tr:1, td:1, th:1, embed:1, object:1, ol:1, ul:1,
 73         a:1, input:1, form:1, select:1, textarea:1, button:1, fieldset:1, thead:1, tfoot:1
 74     };
 75 
 76     S.augment(KESelection, {
 77 
 78 
 79         /**
 80          * Gets the native selection object from the browser.
 81          * @returns {Object} The native selection object.
 82          * @example
 83          * var selection = editor.getSelection().<b>getNative()</b>;
 84          */
 85         getNative:!OLD_IE ?
 86             function () {
 87                 var self = this,
 88                     cache = self._.cache;
 89                 return cache.nativeSel || ( cache.nativeSel = DOM._getWin(self.document).getSelection() );
 90             }
 91             :
 92             function () {
 93                 var self = this, cache = self._.cache;
 94                 return cache.nativeSel || ( cache.nativeSel = self.document.selection );
 95             },
 96 
 97         /**
 98          * Gets the type of the current selection. The following values are
 99          * available:
100          * <ul>
101          *        <li> SELECTION_NONE (1): No selection.</li>
102          *        <li> SELECTION_TEXT (2): Text is selected or
103          *            collapsed selection.</li>
104          *        <li> SELECTION_ELEMENT (3): A element
105          *            selection.</li>
106          * </ul>
107          * @returns {number} One of the following constant values:
108          *         SELECTION_NONE,  SELECTION_TEXT or
109          *         SELECTION_ELEMENT.
110          * @example
111          * if ( editor.getSelection().<b>getType()</b> == SELECTION_TEXT )
112          *     alert( 'Text is selected' );
113          */
114         getType:!OLD_IE ?
115             function () {
116                 var self = this, cache = self._.cache;
117                 if (cache.type)
118                     return cache.type;
119 
120                 var type = KES.SELECTION_TEXT,
121                     sel = self.getNative();
122 
123                 if (!sel)
124                     type = KES.SELECTION_NONE;
125                 else if (sel.rangeCount == 1) {
126                     // Check if the actual selection is a control (IMG,
127                     // TABLE, HR, etc...).
128 
129                     var range = sel.getRangeAt(0),
130                         startContainer = range.startContainer;
131 
132                     if (startContainer == range.endContainer
133                         && startContainer.nodeType == DOM.ELEMENT_NODE
134                         && Number(range.endOffset - range.startOffset) == 1
135                         && styleObjectElements[ startContainer.childNodes[ range.startOffset ].nodeName.toLowerCase() ]) {
136                         type = KES.SELECTION_ELEMENT;
137                     }
138                 }
139 
140                 return ( cache.type = type );
141             } :
142             function () {
143                 var self = this, cache = self._.cache;
144                 if (cache.type)
145                     return cache.type;
146 
147                 var type = KES.SELECTION_NONE;
148 
149                 try {
150                     var sel = self.getNative(),
151                         ieType = sel.type;
152 
153                     if (ieType == 'Text')
154                         type = KES.SELECTION_TEXT;
155 
156                     if (ieType == 'Control')
157                         type = KES.SELECTION_ELEMENT;
158 
159                     // It is possible that we can still get a text range
160                     // object even when type == 'None' is returned by IE.
161                     // So we'd better check the object returned by
162                     // createRange() rather than by looking at the type.
163                     //当前一个操作选中文本,后一个操作右键点了字串中间就会出现了
164                     if (sel.createRange().parentElement)
165                         type = KES.SELECTION_TEXT;
166                 }
167                 catch (e) {
168                 }
169 
170                 return ( cache.type = type );
171             },
172 
173         getRanges:OLD_IE ?
174             (function () {
175                 // Finds the container and offset for a specific boundary
176                 // of an IE range.
177                 /**
178                  *
179                  * @param {TextRange} range
180                  * @param {Boolean=} start
181                  */
182                 var getBoundaryInformation = function (range, start) {
183                     // Creates a collapsed range at the requested boundary.
184                     range = range.duplicate();
185                     range.collapse(start);
186 
187                     // Gets the element that encloses the range entirely.
188                     var parent = range.parentElement(), siblings = parent.childNodes,
189                         testRange;
190 
191                     for (var i = 0; i < siblings.length; i++) {
192                         var child = siblings[ i ];
193 
194                         if (child.nodeType == DOM.ELEMENT_NODE) {
195                             testRange = range.duplicate();
196 
197                             testRange.moveToElementText(child);
198 
199                             var comparisonStart = testRange.compareEndPoints('StartToStart', range),
200                                 comparisonEnd = testRange.compareEndPoints('EndToStart', range);
201 
202                             testRange.collapse();
203                             //中间有其他标签
204                             if (comparisonStart > 0)
205                                 break;
206                             // When selection stay at the side of certain self-closing elements, e.g. BR,
207                             // our comparison will never shows an equality. (#4824)
208                             else if (!comparisonStart
209                                 || comparisonEnd == 1 && comparisonStart == -1)
210                                 return { container:parent, offset:i };
211                             else if (!comparisonEnd)
212                                 return { container:parent, offset:i + 1 };
213 
214                             testRange = NULL;
215                         }
216                     }
217 
218                     if (!testRange) {
219                         testRange = range.duplicate();
220                         testRange.moveToElementText(parent);
221                         testRange.collapse(FALSE);
222                     }
223 
224                     testRange.setEndPoint('StartToStart', range);
225                     // IE report line break as CRLF with range.text but
226                     // only LF with textnode.nodeValue, normalize them to avoid
227                     // breaking character counting logic below. (#3949)
228                     var distance = String(testRange.text)
229                         .replace(/\r\n|\r/g, '\n').length;
230 
231                     try {
232                         while (distance > 0)
233                             //bug? 可能不是文本节点 nodeValue undefined
234                             //永远不会出现 textnode<img/>textnode
235                             //停止时,前面一定为textnode
236                             distance -= siblings[ --i ].nodeValue.length;
237                     }
238                         // Measurement in IE could be somtimes wrong because of <select> element. (#4611)
239                     catch (e) {
240                         distance = 0;
241                     }
242 
243 
244                     if (distance === 0) {
245                         return {
246                             container:parent,
247                             offset:i
248                         };
249                     }
250                     else {
251                         return {
252                             container:siblings[ i ],
253                             offset:-distance
254                         };
255                     }
256                 };
257 
258                 return function (force) {
259                     var self = this, cache = self._.cache;
260                     if (cache.ranges && !force)
261                         return cache.ranges;
262 
263                     // IE doesn't have range support (in the W3C way), so we
264                     // need to do some magic to transform selections into
265                     // Range instances.
266 
267                     var sel = self.getNative(),
268                         nativeRange = sel && sel.createRange(),
269                         type = self.getType(),
270                         range;
271 
272                     if (!sel)
273                         return [];
274 
275                     if (type == KES.SELECTION_TEXT) {
276                         range = new KERange(self.document);
277                         var boundaryInfo = getBoundaryInformation(nativeRange, TRUE);
278                         range.setStart(new Node(boundaryInfo.container), boundaryInfo.offset);
279                         boundaryInfo = getBoundaryInformation(nativeRange);
280                         range.setEnd(new Node(boundaryInfo.container), boundaryInfo.offset);
281                         return ( cache.ranges = [ range ] );
282                     } else if (type == KES.SELECTION_ELEMENT) {
283                         var retval = cache.ranges = [];
284 
285                         for (var i = 0; i < nativeRange.length; i++) {
286                             var element = nativeRange.item(i),
287                                 parentElement = element.parentNode,
288                                 j = 0;
289 
290                             range = new KERange(self.document);
291 
292                             for (; j < parentElement.childNodes.length && parentElement.childNodes[j] != element; j++) { /*jsl:pass*/
293                             }
294 
295                             range.setStart(new Node(parentElement), j);
296                             range.setEnd(new Node(parentElement), j + 1);
297                             retval.push(range);
298                         }
299 
300                         return retval;
301                     }
302 
303                     return ( cache.ranges = [] );
304                 };
305             })()
306             :
307             function (force) {
308                 var self = this, cache = self._.cache;
309                 if (cache.ranges && !force)
310                     return cache.ranges;
311 
312                 // On browsers implementing the W3C range, we simply
313                 // tranform the native ranges in Range
314                 // instances.
315 
316                 var ranges = [], sel = self.getNative();
317 
318                 if (!sel)
319                     return [];
320 
321                 for (var i = 0; i < sel.rangeCount; i++) {
322                     var nativeRange = sel.getRangeAt(i), range = new KERange(self.document);
323 
324                     range.setStart(new Node(nativeRange.startContainer), nativeRange.startOffset);
325                     range.setEnd(new Node(nativeRange.endContainer), nativeRange.endOffset);
326                     ranges.push(range);
327                 }
328 
329                 return ( cache.ranges = ranges );
330             },
331 
332         /**
333          * Gets the DOM element in which the selection starts.
334          * @returns The element at the beginning of the
335          *        selection.
336          * @example
337          * var element = editor.getSelection().<b>getStartElement()</b>;
338          * alert( element.nodeName() );
339          */
340         getStartElement:function () {
341             var self = this, cache = self._.cache;
342             if (cache.startElement !== undefined)
343                 return cache.startElement;
344 
345             var node,
346                 sel = self.getNative();
347 
348             switch (self.getType()) {
349                 case KES.SELECTION_ELEMENT :
350                     return this.getSelectedElement();
351 
352                 case KES.SELECTION_TEXT :
353 
354                     var range = self.getRanges()[0];
355 
356                     if (range) {
357                         if (!range.collapsed) {
358                             range.optimize();
359 
360                             // Decrease the range content to exclude particial
361                             // selected node on the start which doesn't have
362                             // visual impact. ( #3231 )
363                             while (TRUE) {
364                                 var startContainer = range.startContainer,
365                                     startOffset = range.startOffset;
366                                 // Limit the fix only to non-block elements.(#3950)
367                                 if (startOffset == ( startContainer[0].nodeType === DOM.ELEMENT_NODE ?
368                                     startContainer[0].childNodes.length : startContainer[0].nodeValue.length )
369                                     && !startContainer._4e_isBlockBoundary()) {
370                                     range.setStartAfter(startContainer);
371                                 } else {
372                                     break;
373                                 }
374                             }
375 
376                             node = range.startContainer;
377 
378                             if (node[0].nodeType != DOM.ELEMENT_NODE) {
379                                 return node.parent();
380                             }
381 
382                             node = new Node(node[0].childNodes[range.startOffset]);
383 
384                             if (!node[0] || node[0].nodeType != DOM.ELEMENT_NODE) {
385                                 return range.startContainer;
386                             }
387 
388                             var child = node[0].firstChild;
389                             while (child && child.nodeType == DOM.ELEMENT_NODE) {
390                                 node = new Node(child);
391                                 child = child.firstChild;
392                             }
393                             return node;
394                         }
395                     }
396 
397                     if (OLD_IE) {
398                         range = sel.createRange();
399                         range.collapse(TRUE);
400                         node = new Node(range.parentElement());
401                     }
402                     else {
403                         node = sel.anchorNode;
404                         if (node && node.nodeType != DOM.ELEMENT_NODE) {
405                             node = node.parentNode;
406                         }
407                         if (node) {
408                             node = new Node(node);
409                         }
410                     }
411             }
412 
413             return cache.startElement = node;
414         },
415 
416         /**
417          * Gets the current selected element.
418          * @returns The selected element. Null if no
419          *        selection is available or the selection type is not
420          *       SELECTION_ELEMENT.
421          * @example
422          * var element = editor.getSelection().<b>getSelectedElement()</b>;
423          * alert( element.nodeName() );
424          */
425         getSelectedElement:function () {
426             var self = this,
427                 node,
428                 cache = self._.cache;
429 
430             if (cache.selectedElement !== undefined) {
431                 return cache.selectedElement;
432             }
433 
434             // Is it native IE control type selection?
435             if (OLD_IE) {
436                 var range = self.getNative().createRange();
437                 node = range.item && range.item(0);
438             }
439 
440             // Figure it out by checking if there's a single enclosed
441             // node of the range.
442             // 处理 ^  <img/>  ^
443             if (!node) {
444                 node = (function () {
445                     var range = self.getRanges()[ 0 ],
446                         enclosed,
447                         selected;
448 
449                     // 先检查第一层
450                     // <div>^<img/>^</div>
451                     // shrink 再检查
452                     // <div><span>^<img/>^</span></div>
453                     for (var i = 2;
454                          i && !(( enclosed = range.getEnclosedNode() ) &&
455                              ( enclosed[0].nodeType == DOM.ELEMENT_NODE ) &&
456                              // 某些值得这么多的元素??
457                              styleObjectElements[ enclosed.nodeName() ] &&
458                              ( selected = enclosed ));
459                          i--) {
460                         // Then check any deep wrapped element
461                         // e.g. [<b><i><img /></i></b>]
462                         // 一下子退到底  ^<a><span><span><img/></span></span></a>^
463                         // ->
464                         //<a><span><span>^<img/>^</span></span></a>
465                         range.shrink(KER.SHRINK_ELEMENT);
466                     }
467 
468                     return  selected;
469                 })();
470             } else {
471                 node = new Node(node);
472             }
473 
474             return cache.selectedElement = node;
475         },
476 
477 
478         reset:function () {
479             this._.cache = {};
480         },
481 
482         selectElement:function (element) {
483             var range,
484                 self = this,
485                 doc = self.document;
486             if (OLD_IE) {
487                 //do not use empty(),编辑器内滚动条重置了
488                 //选择的 img 内容前后莫名被清除
489                 //self.getNative().empty();
490                 try {
491                     // Try to select the node as a control.
492                     range = doc.body['createControlRange']();
493                     range['addElement'](element[0]);
494                     range.select();
495                 } catch (e) {
496                     // If failed, select it as a text range.
497                     range = doc.body.createTextRange();
498                     range.moveToElementText(element[0]);
499                     range.select();
500                 } finally {
501                     // fire('selectionChange');
502                 }
503                 self.reset();
504             } else {
505                 // Create the range for the element.
506                 range = doc.createRange();
507                 range.selectNode(element[0]);
508                 // Select the range.
509                 var sel = self.getNative();
510                 sel.removeAllRanges();
511                 sel.addRange(range);
512                 self.reset();
513             }
514         },
515 
516         selectRanges:function (ranges) {
517             var self = this;
518             if (OLD_IE) {
519                 if (ranges.length > 1) {
520                     // IE doesn't accept multiple ranges selection, so we join all into one.
521                     var last = ranges[ ranges.length - 1 ];
522                     ranges[ 0 ].setEnd(last.endContainer, last.endOffset);
523                     ranges.length = 1;
524                 }
525 
526                 // IE doesn't accept multiple ranges selection, so we just
527                 // select the first one.
528                 if (ranges[ 0 ])
529                     ranges[ 0 ].select();
530 
531                 self.reset();
532             }
533             else {
534                 var sel = self.getNative();
535                 if (!sel) {
536                     return;
537                 }
538                 sel.removeAllRanges();
539                 for (var i = 0; i < ranges.length; i++) {
540                     var range = ranges[ i ],
541                         nativeRange = self.document.createRange(),
542                         startContainer = range.startContainer;
543 
544                     // In FF2, if we have a collapsed range, inside an empty
545                     // element, we must add something to it otherwise the caret
546                     // will not be visible.
547                     // opera move out of this element
548                     if (range.collapsed &&
549                         (( UA.gecko && UA.gecko < 1.0900 ) || UA.opera || UA['webkit']) &&
550                         startContainer[0].nodeType == DOM.ELEMENT_NODE &&
551                         !startContainer[0].childNodes.length) {
552                         // webkit 光标停留不到在空元素内,要fill char,之后范围定在 fillchar 之后
553                         startContainer[0].appendChild(self.document.createTextNode(UA['webkit'] ? "\u200b" : ""));
554                         range.startOffset++;
555                         range.endOffset++;
556                     }
557 
558                     nativeRange.setStart(startContainer[0], range.startOffset);
559                     nativeRange.setEnd(range.endContainer[0], range.endOffset);
560                     // Select the range.
561                     sel.addRange(nativeRange);
562                 }
563                 self.reset();
564             }
565         },
566         createBookmarks2:function (normalized) {
567             var bookmarks = [],
568                 ranges = this.getRanges();
569 
570             for (var i = 0; i < ranges.length; i++)
571                 bookmarks.push(ranges[i].createBookmark2(normalized));
572 
573             return bookmarks;
574         },
575         createBookmarks:function (serializable, ranges) {
576             var self = this,
577                 retval = [],
578                 doc = self.document,
579                 bookmark;
580             ranges = ranges || self.getRanges();
581             var length = ranges.length;
582             for (var i = 0; i < length; i++) {
583                 retval.push(bookmark = ranges[ i ].createBookmark(serializable, TRUE));
584                 serializable = bookmark.serializable;
585 
586                 var bookmarkStart = serializable ? S.one("#" + bookmark.startNode, doc) : bookmark.startNode,
587                     bookmarkEnd = serializable ? S.one("#" + bookmark.endNode, doc) : bookmark.endNode;
588 
589                 // Updating the offset values for rest of ranges which have been mangled(#3256).
590                 for (var j = i + 1; j < length; j++) {
591                     var dirtyRange = ranges[ j ],
592                         rangeStart = dirtyRange.startContainer,
593                         rangeEnd = dirtyRange.endContainer;
594 
595                     DOM.equals(rangeStart, bookmarkStart.parent()) && dirtyRange.startOffset++;
596                     DOM.equals(rangeStart, bookmarkEnd.parent()) && dirtyRange.startOffset++;
597                     DOM.equals(rangeEnd, bookmarkStart.parent()) && dirtyRange.endOffset++;
598                     DOM.equals(rangeEnd, bookmarkEnd.parent()) && dirtyRange.endOffset++;
599                 }
600             }
601 
602             return retval;
603         },
604 
605         selectBookmarks:function (bookmarks) {
606             var self = this, ranges = [];
607             for (var i = 0; i < bookmarks.length; i++) {
608                 var range = new KERange(self.document);
609                 range.moveToBookmark(bookmarks[i]);
610                 ranges.push(range);
611             }
612             self.selectRanges(ranges);
613             return self;
614         },
615 
616         getCommonAncestor:function () {
617             var ranges = this.getRanges(),
618                 startNode = ranges[ 0 ].startContainer,
619                 endNode = ranges[ ranges.length - 1 ].endContainer;
620             return startNode._4e_commonAncestor(endNode);
621         },
622 
623         // Moving scroll bar to the current selection's start position.
624         scrollIntoView:function () {
625             // If we have split the block, adds a temporary span at the
626             // range position and scroll relatively to it.
627             var start = this.getStartElement();
628             start && start.scrollIntoView(undefined, false);
629         },
630         removeAllRanges:function () {
631             var sel = this.getNative();
632             if (!OLD_IE) {
633                 sel && sel.removeAllRanges();
634             } else {
635                 sel && sel.clear();
636             }
637         }
638     });
639 
640 
641     var nonCells = { table:1, tbody:1, tr:1 }, notWhitespaces = Walker.whitespaces(TRUE),
642         fillerTextRegex = /\ufeff|\u00a0/;
643     KERange.prototype["select"] =
644         KERange.prototype.select =
645             !OLD_IE ? function () {
646                 var self = this, startContainer = self.startContainer;
647 
648                 // If we have a collapsed range, inside an empty element, we must add
649                 // something to it, otherwise the caret will not be visible.
650                 if (self.collapsed && startContainer[0].nodeType == DOM.ELEMENT_NODE && !startContainer[0].childNodes.length)
651                     startContainer[0].appendChild(self.document.createTextNode(""));
652 
653                 var nativeRange = self.document.createRange();
654                 nativeRange.setStart(startContainer[0], self.startOffset);
655 
656                 try {
657                     nativeRange.setEnd(self.endContainer[0], self.endOffset);
658                 } catch (e) {
659                     // There is a bug in Firefox implementation (it would be too easy
660                     // otherwise). The new start can't be after the end (W3C says it can).
661                     // So, let's create a new range and collapse it to the desired point.
662                     if (e.toString().indexOf('NS_ERROR_ILLEGAL_VALUE') >= 0) {
663                         self.collapse(TRUE);
664                         nativeRange.setEnd(self.endContainer[0], self.endOffset);
665                     }
666                     else
667                         throw( e );
668                 }
669 
670                 var selection = getSelection(self.document).getNative();
671                 selection.removeAllRanges();
672                 selection.addRange(nativeRange);
673             } : // V2
674                 function (forceExpand) {
675 
676                     var self = this,
677                         collapsed = self.collapsed,
678                         isStartMarkerAlone,
679                         dummySpan;
680                     //选的是元素,直接使用selectElement
681                     //还是有差异的,特别是img选择框问题
682                     if (
683                     //ie8 有问题??
684                     //UA['ie']Engine!=8 &&
685                         self.startContainer[0] === self.endContainer[0]
686                             && self.endOffset - self.startOffset == 1) {
687                         var selEl = self.startContainer[0].childNodes[self.startOffset];
688                         if (selEl.nodeType == DOM.ELEMENT_NODE) {
689                             new KESelection(self.document).selectElement(new Node(selEl));
690                             return;
691                         }
692                     }
693                     // IE doesn't support selecting the entire table row/cell, move the selection into cells, e.g.
694                     // <table><tbody><tr>[<td>cell</b></td>... => <table><tbody><tr><td>[cell</td>...
695                     if (self.startContainer[0].nodeType == DOM.ELEMENT_NODE &&
696                         self.startContainer.nodeName() in nonCells
697                         || self.endContainer[0].nodeType == DOM.ELEMENT_NODE &&
698                         self.endContainer.nodeName() in nonCells) {
699                         self.shrink(KER.SHRINK_ELEMENT, TRUE);
700                     }
701 
702                     var bookmark = self.createBookmark(),
703                     // Create marker tags for the start and end boundaries.
704                         startNode = bookmark.startNode,
705                         endNode;
706                     if (!collapsed)
707                         endNode = bookmark.endNode;
708 
709                     // Create the main range which will be used for the selection.
710                     var ieRange = self.document.body.createTextRange();
711 
712                     // Position the range at the start boundary.
713                     ieRange.moveToElementText(startNode[0]);
714                     //跳过开始 bookmark 标签
715                     ieRange.moveStart('character', 1);
716 
717                     if (endNode) {
718                         // Create a tool range for the end.
719                         var ieRangeEnd = self.document.body.createTextRange();
720                         // Position the tool range at the end.
721                         ieRangeEnd.moveToElementText(endNode[0]);
722                         // Move the end boundary of the main range to match the tool range.
723                         ieRange.setEndPoint('EndToEnd', ieRangeEnd);
724                         ieRange.moveEnd('character', -1);
725                     }
726                     else {
727                         // The isStartMarkerAlone logic comes from V2. It guarantees that the lines
728                         // will expand and that the cursor will be blinking on the right place.
729                         // Actually, we are using this flag just to avoid using this hack in all
730                         // situations, but just on those needed.
731                         var next = startNode[0].nextSibling;
732                         while (next && !notWhitespaces(next)) {
733                             next = next.nextSibling;
734                         }
735                         isStartMarkerAlone =
736                             (
737                                 !( next && next.nodeValue && next.nodeValue.match(fillerTextRegex) )     // already a filler there?
738                                     && ( forceExpand
739                                     ||
740                                     !startNode[0].previousSibling
741                                     ||
742                                     (
743                                         startNode[0].previousSibling &&
744                                             DOM.nodeName(startNode[0].previousSibling) == 'br'
745                                         )
746                                     )
747                                 );
748 
749                         // Append a temporary <span></span> before the selection.
750                         // This is needed to avoid IE destroying selections inside empty
751                         // inline elements, like <b></b> (#253).
752                         // It is also needed when placing the selection right after an inline
753                         // element to avoid the selection moving inside of it.
754                         dummySpan = new Node(self.document.createElement('span'));
755                         dummySpan.html('');	// Zero Width No-Break Space (U+FEFF). See #1359.
756                         dummySpan.insertBefore(startNode);
757                         if (isStartMarkerAlone) {
758                             // To expand empty blocks or line spaces after <br>, we need
759                             // instead to have any char, which will be later deleted using the
760                             // selection.
761                             // \ufeff = Zero Width No-Break Space (U+FEFF). (#1359)
762                             DOM.insertBefore(self.document.createTextNode('\ufeff'), startNode[0] || startNode);
763                         }
764                     }
765 
766                     // Remove the markers (reset the position, because of the changes in the DOM tree).
767                     self.setStartBefore(startNode);
768                     startNode._4e_remove();
769 
770                     if (collapsed) {
771                         if (isStartMarkerAlone) {
772                             // Move the selection start to include the temporary \ufeff.
773                             ieRange.moveStart('character', -1);
774                             ieRange.select();
775                             // Remove our temporary stuff.
776                             self.document.selection.clear();
777                         } else
778                             ieRange.select();
779                         if (dummySpan) {
780                             self.moveToPosition(dummySpan, KER.POSITION_BEFORE_START);
781                             dummySpan._4e_remove();
782                         }
783                     }
784                     else {
785                         self.setEndBefore(endNode);
786                         endNode._4e_remove();
787                         ieRange.select();
788                     }
789                     // fire('selectionChange');
790                 };
791 
792 
793     function getSelection(doc) {
794         var sel = new KESelection(doc);
795         return ( !sel || sel.isInvalid ) ? NULL : sel;
796     }
797 
798     KESelection.getSelection = getSelection;
799 
800     Editor.Selection = KESelection;
801 
802     return KESelection;
803 }, {
804     requires:['./base', './walker', './range', './dom']
805 });
806