1 /**
  2  * Range implementation across browsers for kissy editor. Modified from CKEditor.
  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/range", function (S, Editor, Utils, Walker, ElementPath) {
 10     /**
 11      * Enum for range
 12      * @enum {number}
 13      */
 14     Editor.RANGE = {
 15         POSITION_AFTER_START:1, // <element>^contents</element>		"^text"
 16         POSITION_BEFORE_END:2, // <element>contents^</element>		"text^"
 17         POSITION_BEFORE_START:3, // ^<element>contents</element>		^"text"
 18         POSITION_AFTER_END:4, // <element>contents</element>^		"text"^
 19         ENLARGE_ELEMENT:1,
 20         ENLARGE_BLOCK_CONTENTS:2,
 21         ENLARGE_LIST_ITEM_CONTENTS:3,
 22         START:1,
 23         END:2,
 24         SHRINK_ELEMENT:1,
 25         SHRINK_TEXT:2
 26     };
 27 
 28     var TRUE = true,
 29         FALSE = false,
 30         NULL = null,
 31         KER = Editor.RANGE,
 32         KEP = Editor.POSITION,
 33         DOM = S.DOM,
 34         UA = S.UA,
 35         dtd = Editor.XHTML_DTD,
 36         Node = S.Node,
 37         $ = Node.all,
 38         EMPTY = {area:1, base:1, br:1, col:1, hr:1, img:1, input:1, link:1, meta:1, param:1};
 39 
 40     var isWhitespace = new Walker.whitespaces(),
 41         isBookmark = new Walker.bookmark(),
 42         isNotWhitespaces = Walker.whitespaces(TRUE),
 43         isNotBookmarks = Walker.bookmark(false, true);
 44 
 45     var inlineChildReqElements = { abbr:1, acronym:1, b:1, bdo:1,
 46         big:1, cite:1, code:1, del:1, dfn:1,
 47         em:1, font:1, i:1, ins:1, label:1,
 48         kbd:1, q:1, samp:1, small:1, span:1,
 49         strike:1, strong:1, sub:1, sup:1, tt:1, u:1, 'var':1 };
 50 
 51     // Evaluator for checkBoundaryOfElement, reject any
 52     // text node and non-empty elements unless it's being bookmark text.
 53     function elementBoundaryEval(node) {
 54         // Reject any text node unless it's being bookmark
 55         // OR it's spaces. (#3883)
 56         // 如果不是文本节点并且是空的,可以继续取下一个判断边界
 57         var c1 = node.nodeType != DOM.TEXT_NODE &&
 58                 DOM.nodeName(node) in dtd.$removeEmpty,
 59         // 文本为空,可以继续取下一个判断边界
 60             c2 = node.nodeType == DOM.TEXT_NODE && !S.trim(node.nodeValue),
 61         // 恩,进去了书签,可以继续取下一个判断边界
 62             c3 = !!node.parentNode.getAttribute('_ke_bookmark');
 63         return c1 || c2 || c3;
 64     }
 65 
 66     function nonWhitespaceOrIsBookmark(node) {
 67         // Whitespaces and bookmark nodes are to be ignored.
 68         return !isWhitespace(node) && !isBookmark(node);
 69     }
 70 
 71     function getCheckStartEndBlockEvalFunction(isStart) {
 72         var hadBr = FALSE;
 73         return function (node) {
 74             // First ignore bookmark nodes.
 75             if (isBookmark(node))
 76                 return TRUE;
 77 
 78             if (node.nodeType == DOM.TEXT_NODE) {
 79                 // If there's any visible text, then we're not at the start.
 80                 if (S.trim(node.nodeValue).length) {
 81                     return FALSE;
 82                 }
 83             } else if (node.nodeType == DOM.ELEMENT_NODE) {
 84                 var nodeName = DOM.nodeName(node);
 85                 // If there are non-empty inline elements (e.g. <img />), then we're not
 86                 // at the start.
 87                 if (!inlineChildReqElements[ nodeName ]) {
 88                     // If we're working at the end-of-block, forgive the first <br /> in non-IE
 89                     // browsers.
 90                     if (!isStart && !UA['ie'] && nodeName == 'br' && !hadBr) {
 91                         hadBr = TRUE;
 92                     } else {
 93                         return FALSE;
 94                     }
 95                 }
 96             }
 97             return TRUE;
 98         };
 99     }
100 
101 
102     /**
103      * Extract html content within range.
104      * @param {Number} action
105      * 0 : delete
106      * 1 : extract
107      * 2 : clone
108      */
109     function execContentsAction(self, action) {
110         var startNode = self.startContainer,
111             endNode = self.endContainer,
112             startOffset = self.startOffset,
113             endOffset = self.endOffset,
114             removeStartNode,
115             hasSplitStart = FALSE,
116             hasSplitEnd = FALSE,
117             t,
118             docFrag = undefined,
119             doc = self.document,
120             removeEndNode;
121 
122         if (action > 0) {
123             docFrag = doc.createDocumentFragment();
124         }
125 
126         if (self.collapsed) {
127             return docFrag;
128         }
129 
130         // 将 bookmark 包含在选区内
131         self.optimizeBookmark();
132 
133 
134         // endNode -> end guard , not included in range
135 
136         // For text containers, we must simply split the node and point to the
137         // second part. The removal will be handled by the rest of the code .
138         //最关键:一般起始都是在文字节点中,得到起点选择右边的文字节点,只对节点处理!
139         if (endNode[0].nodeType == DOM.TEXT_NODE) {
140             hasSplitEnd = TRUE;
141             endNode = endNode._4e_splitText(endOffset);
142         } else {
143             // If the end container has children and the offset is pointing
144             // to a child, then we should start from it.
145             if (endNode[0].childNodes.length > 0) {
146                 // If the offset points after the last node.
147                 if (endOffset >= endNode[0].childNodes.length) {
148                     // Let's create a temporary node and mark it for removal.
149                     endNode = new Node(
150                         endNode[0].appendChild(doc.createTextNode(""))
151                     );
152                     removeEndNode = TRUE;
153                 } else {
154                     endNode = new Node(endNode[0].childNodes[endOffset]);
155                 }
156             }
157         }
158 
159         // startNode -> start guard , not included in range
160 
161         // For text containers, we must simply split the node. The removal will
162         // be handled by the rest of the code .
163         if (startNode[0].nodeType == DOM.TEXT_NODE) {
164             hasSplitStart = TRUE;
165             startNode._4e_splitText(startOffset);
166         } else {
167             // If the start container has children and the offset is pointing
168             // to a child, then we should start from its previous sibling.
169 
170             // If the offset points to the first node, we don't have a
171             // sibling, so let's use the first one, but mark it for removal.
172             if (!startOffset) {
173                 // Let's create a temporary node and mark it for removal.
174                 t = new Node(doc.createTextNode(""));
175                 startNode.prepend(t);
176                 startNode = t;
177                 removeStartNode = TRUE;
178             }
179             else if (startOffset >= startNode[0].childNodes.length) {
180                 // Let's create a temporary node and mark it for removal.
181                 startNode = new Node(startNode[0]
182                     .appendChild(doc.createTextNode('')));
183                 removeStartNode = TRUE;
184             } else
185                 startNode = new Node(
186                     startNode[0].childNodes[startOffset].previousSibling
187                 );
188         }
189 
190         // Get the parent nodes tree for the start and end boundaries.
191         //从根到自己
192         var startParents = startNode._4e_parents(),
193             endParents = endNode._4e_parents();
194 
195         startParents.each(function (n, i) {
196             startParents[i] = n;
197         });
198 
199         endParents.each(function (n, i) {
200             endParents[i] = n;
201         });
202 
203 
204         // Compare them, to find the top most siblings.
205         var i, topStart, topEnd;
206 
207         for (i = 0; i < startParents.length; i++) {
208             topStart = startParents[ i ];
209             topEnd = endParents[ i ];
210 
211             // The compared nodes will match until we find the top most
212             // siblings (different nodes that have the same parent).
213             // "i" will hold the index in the parents array for the top
214             // most element.
215             if (!topStart.equals(topEnd)) {
216                 break;
217             }
218         }
219 
220         var clone = docFrag,
221             levelStartNode,
222             levelClone,
223             currentNode,
224             currentSibling;
225 
226         // Remove all successive sibling nodes for every node in the
227         // startParents tree.
228         for (var j = i; j < startParents.length; j++) {
229             levelStartNode = startParents[j];
230 
231             // For Extract and Clone, we must clone this level.
232             if (action > 0 && !levelStartNode.equals(startNode)) {
233                 // action = 0 = Delete
234                 levelClone = clone.appendChild(levelStartNode.clone()[0]);
235             } else {
236                 levelClone = null;
237             }
238 
239             // 开始节点的路径所在父节点不能 clone(TRUE),其他节点(结束节点路径左边的节点)可以直接 clone(true)
240             currentNode = levelStartNode[0].nextSibling;
241 
242             var endParentJ = endParents[ j ],
243                 domEndNode = endNode[0],
244                 domEndParentJ = endParentJ && endParentJ[0];
245 
246             while (currentNode) {
247                 // Stop processing when the current node matches a node in the
248                 // endParents tree or if it is the endNode.
249                 if (domEndParentJ == currentNode || domEndNode == currentNode) {
250                     break;
251                 }
252 
253                 // Cache the next sibling.
254                 currentSibling = currentNode.nextSibling;
255 
256                 // If cloning, just clone it.
257                 if (action == 2) {
258                     // 2 = Clone
259                     clone.appendChild(currentNode.cloneNode(TRUE));
260                 } else {
261                     // Both Delete and Extract will remove the node.
262                     DOM._4e_remove(currentNode);
263 
264                     // When Extracting, move the removed node to the docFrag.
265                     if (action == 1) {
266                         // 1 = Extract
267                         clone.appendChild(currentNode);
268                     }
269                 }
270 
271                 currentNode = currentSibling;
272             }
273             // 开始节点的路径所在父节点不能 clone(TRUE),要在后面深入子节点处理
274             if (levelClone) {
275                 clone = levelClone;
276             }
277         }
278 
279         clone = docFrag;
280 
281         // Remove all previous sibling nodes for every node in the
282         // endParents tree.
283         for (var k = i; k < endParents.length; k++) {
284             levelStartNode = endParents[ k ];
285 
286             // For Extract and Clone, we must clone this level.
287             if (action > 0 && !levelStartNode.equals(endNode)) {
288                 // action = 0 = Delete
289                 // 浅复制
290                 levelClone = clone.appendChild(levelStartNode.clone()[0]);
291             } else {
292                 levelClone = null;
293             }
294 
295             // The processing of siblings may have already been done by the parent.
296             if (
297                 !startParents[ k ] ||
298                     // 前面 startParents 循环已经处理过了
299                     !levelStartNode._4e_sameLevel(startParents[ k ])
300                 ) {
301                 currentNode = levelStartNode[0].previousSibling;
302                 while (currentNode) {
303                     // Cache the next sibling.
304                     currentSibling = currentNode.previousSibling;
305 
306                     // If cloning, just clone it.
307                     if (action == 2) {    // 2 = Clone
308                         clone.insertBefore(currentNode.cloneNode(TRUE),
309                             clone.firstChild);
310                     } else {
311                         // Both Delete and Extract will remove the node.
312                         DOM._4e_remove(currentNode);
313 
314                         // When Extracting, mode the removed node to the docFrag.
315                         if (action == 1) {
316                             // 1 = Extract
317                             clone.insertBefore(currentNode, clone.firstChild);
318                         }
319                     }
320 
321                     currentNode = currentSibling;
322                 }
323             }
324 
325             if (levelClone) {
326                 clone = levelClone;
327             }
328         }
329         // 2 = Clone.
330         if (action == 2) {
331 
332             // No changes in the DOM should be done, so fix the split text (if any).
333 
334             if (hasSplitStart) {
335                 var startTextNode = startNode[0];
336                 if (startTextNode.nodeType == DOM.TEXT_NODE
337                     && startTextNode.nextSibling
338                     // careful, next sibling should be text node
339                     && startTextNode.nextSibling.nodeType == DOM.TEXT_NODE) {
340                     startTextNode.data += startTextNode.nextSibling.data;
341                     startTextNode.parentNode.removeChild(startTextNode.nextSibling);
342                 }
343             }
344 
345             if (hasSplitEnd) {
346                 var endTextNode = endNode[0];
347                 if (endTextNode.nodeType == DOM.TEXT_NODE &&
348                     endTextNode.previousSibling &&
349                     endTextNode.previousSibling.nodeType == DOM.TEXT_NODE) {
350                     endTextNode.previousSibling.data += endTextNode.data;
351                     endTextNode.parentNode.removeChild(endTextNode);
352                 }
353             }
354 
355         } else {
356 
357             // Collapse the range.
358             // If a node has been partially selected, collapse the range between
359             // topStart and topEnd. Otherwise, simply collapse it to the start.
360             // (W3C specs).
361             if (
362                 topStart && topEnd &&
363                     (
364                         !startNode._4e_sameLevel(topStart)
365                             ||
366                             !endNode._4e_sameLevel(topEnd)
367                         )
368                 ) {
369                 var startIndex = topStart._4e_index();
370 
371                 // If the start node is to be removed, we must correct the
372                 // index to reflect the removal.
373                 if (removeStartNode &&
374                     // startNode 和 topStart 同级
375                     (topStart._4e_sameLevel(startNode))) {
376                     startIndex--;
377                 }
378 
379                 self.setStart(topStart.parent(), startIndex + 1);
380             }
381 
382             // Collapse it to the start.
383             self.collapse(TRUE);
384 
385         }
386 
387         // Cleanup any marked node.
388         if (removeStartNode) {
389             startNode.remove();
390         }
391 
392         if (removeEndNode) {
393             endNode.remove();
394         }
395 
396         return docFrag;
397     }
398 
399     function updateCollapsed(self) {
400         self.collapsed = (
401             self.startContainer &&
402                 self.endContainer &&
403                 self.startContainer[0] == self.endContainer[0] &&
404                 self.startOffset == self.endOffset );
405     }
406 
407 
408     /**
409      * @memberOf Editor
410      * @class
411      * Range implementation across browsers.
412      * @param document {Document}
413      * @name Range
414      */
415     function KERange(document) {
416         var self = this;
417         self.startContainer = NULL;
418         self.startOffset = NULL;
419         self.endContainer = NULL;
420         self.endOffset = NULL;
421         self.collapsed = TRUE;
422         self.document = document;
423     }
424 
425     S.augment(KERange,
426         /**
427          * @lends Editor.Range
428          */
429         {
430 
431             /**
432              * Range string representation.
433              */
434             toString:function () {
435                 var s = [],
436                     self = this,
437                     startContainer = self.startContainer[0],
438                     endContainer = self.endContainer[0];
439                 s.push((startContainer.id || startContainer.nodeName) + ":" + self.startOffset);
440                 s.push((endContainer.id || endContainer.nodeName) + ":" + self.endOffset);
441                 return s.join("<br/>");
442             },
443 
444             /**
445              * Transforms the startContainer and endContainer properties from text
446              * nodes to element nodes, whenever possible. This is actually possible
447              * if either of the boundary containers point to a text node, and its
448              * offset is set to zero, or after the last char in the node.
449              */
450             optimize:function () {
451                 var self = this,
452                     container = self.startContainer,
453                     offset = self.startOffset;
454 
455                 if (container[0].nodeType != DOM.ELEMENT_NODE) {
456                     if (!offset) {
457                         self.setStartBefore(container);
458                     } else if (offset >= container[0].nodeValue.length) {
459                         self.setStartAfter(container);
460                     }
461                 }
462 
463                 container = self.endContainer;
464                 offset = self.endOffset;
465 
466                 if (container[0].nodeType != DOM.ELEMENT_NODE) {
467                     if (!offset) {
468                         self.setEndBefore(container);
469                     } else if (offset >= container[0].nodeValue.length) {
470                         self.setEndAfter(container);
471                     }
472                 }
473             },
474 
475             /**
476              * Set range start after node
477              * @param {NodeList} node
478              */
479             setStartAfter:function (node) {
480                 this.setStart(node.parent(), node._4e_index() + 1);
481             },
482             /**
483              * Set range start before node
484              * @param {NodeList} node
485              */
486             setStartBefore:function (node) {
487                 this.setStart(node.parent(), node._4e_index());
488             },
489             /**
490              * Set range end after node
491              * @param {NodeList} node
492              */
493             setEndAfter:function (node) {
494                 this.setEnd(node.parent(), node._4e_index() + 1);
495             },
496             /**
497              * Set range end before node
498              * @param {NodeList} node
499              */
500             setEndBefore:function (node) {
501                 this.setEnd(node.parent(), node._4e_index());
502             },
503 
504             /**
505              * Make edge bookmarks included in current range.
506              */
507             optimizeBookmark:function () {
508                 var self = this,
509                     startNode = self.startContainer,
510                     endNode = self.endContainer;
511 
512                 if (startNode &&
513                     startNode.nodeName() == 'span' &&
514                     startNode.attr('_ke_bookmark')) {
515                     self.setStartBefore(startNode);
516                 }
517                 if (endNode &&
518                     endNode.nodeName() == 'span' &&
519                     endNode.attr('_ke_bookmark')) {
520                     self.setEndAfter(endNode);
521                 }
522             },
523 
524             /**
525              * Sets the start position of a Range.
526              * @param {NodeList} startNode The node to start the range.
527              * @param {Number} startOffset An integer greater than or equal to zero
528              *        representing the offset for the start of the range from the start
529              *        of startNode.
530              */
531             setStart:function (startNode, startOffset) {
532                 // W3C requires a check for the new position. If it is after the end
533                 // boundary, the range should be collapsed to the new start. It seams
534                 // we will not need this check for our use of this class so we can
535                 // ignore it for now.
536 
537                 // Fixing invalid range start inside dtd empty elements.
538                 var self = this;
539                 if (startNode[0].nodeType == DOM.ELEMENT_NODE && EMPTY[ startNode.nodeName() ]) {
540                     startNode = startNode.parent();
541                     startOffset = startNode._4e_index();
542                 }
543 
544                 self.startContainer = startNode;
545                 self.startOffset = startOffset;
546 
547                 if (!self.endContainer) {
548                     self.endContainer = startNode;
549                     self.endOffset = startOffset;
550                 }
551 
552                 updateCollapsed(self);
553             },
554 
555             /**
556              * Sets the end position of a Range.
557              * @param {NodeList} endNode The node to end the range.
558              * @param {Number} endOffset An integer greater than or equal to zero
559              *        representing the offset for the end of the range from the start
560              *        of endNode.
561              */
562             setEnd:function (endNode, endOffset) {
563                 // W3C requires a check for the new position. If it is before the start
564                 // boundary, the range should be collapsed to the new end. It seams we
565                 // will not need this check for our use of this class so we can ignore
566                 // it for now.
567 
568                 // Fixing invalid range end inside dtd empty elements.
569                 var self = this;
570                 if (endNode[0].nodeType == DOM.ELEMENT_NODE && EMPTY[ endNode.nodeName() ]) {
571                     endNode = endNode.parent();
572                     endOffset = endNode._4e_index() + 1;
573                 }
574 
575                 self.endContainer = endNode;
576                 self.endOffset = endOffset;
577 
578                 if (!self.startContainer) {
579                     self.startContainer = endNode;
580                     self.startOffset = endOffset;
581                 }
582 
583                 updateCollapsed(self);
584             },
585 
586             /**
587              * Sets the start position of a Range by specified rules.
588              * @param {NodeList} node
589              * @param {Number} position
590              */
591             setStartAt:function (node, position) {
592                 var self = this;
593                 switch (position) {
594                     case KER.POSITION_AFTER_START :
595                         self.setStart(node, 0);
596                         break;
597 
598                     case KER.POSITION_BEFORE_END :
599                         if (node[0].nodeType == DOM.TEXT_NODE) {
600                             self.setStart(node, node[0].nodeValue.length);
601                         } else {
602                             self.setStart(node, node[0].childNodes.length);
603                         }
604                         break;
605 
606                     case KER.POSITION_BEFORE_START :
607                         self.setStartBefore(node);
608                         break;
609 
610                     case KER.POSITION_AFTER_END :
611                         self.setStartAfter(node);
612                 }
613 
614                 updateCollapsed(self);
615             },
616 
617             /**
618              * Sets the end position of a Range by specified rules.
619              * @param {NodeList} node
620              * @param {Number} position
621              */
622             setEndAt:function (node, position) {
623                 var self = this;
624                 switch (position) {
625                     case KER.POSITION_AFTER_START :
626                         self.setEnd(node, 0);
627                         break;
628 
629                     case KER.POSITION_BEFORE_END :
630                         if (node[0].nodeType == DOM.TEXT_NODE) {
631                             self.setEnd(node, node[0].nodeValue.length);
632                         } else {
633                             self.setEnd(node, node[0].childNodes.length);
634                         }
635                         break;
636 
637                     case KER.POSITION_BEFORE_START :
638                         self.setEndBefore(node);
639                         break;
640 
641                     case KER.POSITION_AFTER_END :
642                         self.setEndAfter(node);
643                 }
644 
645                 updateCollapsed(self);
646             },
647 
648             /**
649              * Clone html content within range
650              */
651             cloneContents:function () {
652                 return execContentsAction(this, 2);
653             },
654 
655             /**
656              * Remove html content within range
657              */
658             deleteContents:function () {
659                 return execContentsAction(this, 0);
660             },
661 
662             /**
663              * Extract html content within range.
664              */
665             extractContents:function () {
666                 return execContentsAction(this, 1);
667             },
668 
669             /**
670              * Collpase current range
671              * @param {Boolean} toStart
672              */
673             collapse:function (toStart) {
674                 var self = this;
675                 if (toStart) {
676                     self.endContainer = self.startContainer;
677                     self.endOffset = self.startOffset;
678                 } else {
679                     self.startContainer = self.endContainer;
680                     self.startOffset = self.endOffset;
681                 }
682                 self.collapsed = TRUE;
683             },
684 
685             /**
686              * Clone current range.
687              * @return {Editor.Range}
688              */
689             clone:function () {
690                 var self = this,
691                     clone = new KERange(self.document);
692 
693                 clone.startContainer = self.startContainer;
694                 clone.startOffset = self.startOffset;
695                 clone.endContainer = self.endContainer;
696                 clone.endOffset = self.endOffset;
697                 clone.collapsed = self.collapsed;
698 
699                 return clone;
700             },
701 
702             /**
703              * Get node which is enclosed by range.
704              * @example
705              * <code>
706              * ^<book/><span/><book/>^
707              * =>
708              * ^<span/>^
709              * </code>
710              */
711             getEnclosedNode:function () {
712                 var walkerRange = this.clone();
713 
714                 // Optimize and analyze the range to avoid DOM destructive nature of walker.
715                 walkerRange.optimize();
716 
717                 if (walkerRange.startContainer[0].nodeType != DOM.ELEMENT_NODE ||
718                     walkerRange.endContainer[0].nodeType != DOM.ELEMENT_NODE) {
719                     return NULL;
720                 }
721 
722                 var walker = new Walker(walkerRange),
723                     node, pre;
724 
725                 walker.evaluator = function (node) {
726                     return isNotWhitespaces(node) && isNotBookmarks(node);
727                 };
728 
729                 //深度优先遍历的第一个元素
730                 //        x
731                 //     y     z
732                 // x->y ,return y
733                 node = walker.next();
734                 walker.reset();
735                 pre = walker.previous();
736                 //前后相等,则脱一层皮 :)
737                 return node && node.equals(pre) ? node : NULL;
738             },
739 
740             /**
741              * Shrink range to its innermost element.(make sure text content is unchanged)
742              * @param mode
743              * @param {Boolean} [selectContents]
744              */
745             shrink:function (mode, selectContents) {
746                 // Unable to shrink a collapsed range.
747                 var self = this;
748                 if (!self.collapsed) {
749                     mode = mode || KER.SHRINK_TEXT;
750 
751                     var walkerRange = self.clone(),
752                         startContainer = self.startContainer,
753                         endContainer = self.endContainer,
754                         startOffset = self.startOffset,
755                         endOffset = self.endOffset,
756                     // Whether the start/end boundary is movable.
757                         moveStart = TRUE,
758                         currentElement,
759                         walker,
760                         moveEnd = TRUE;
761 
762                     if (startContainer &&
763                         startContainer[0].nodeType == DOM.TEXT_NODE) {
764                         if (!startOffset) {
765                             walkerRange.setStartBefore(startContainer);
766                         } else if (startOffset >= startContainer[0].nodeValue.length) {
767                             walkerRange.setStartAfter(startContainer);
768                         } else {
769                             // Enlarge the range properly to avoid walker making
770                             // DOM changes caused by trimming the text nodes later.
771                             walkerRange.setStartBefore(startContainer);
772                             moveStart = FALSE;
773                         }
774                     }
775 
776                     if (endContainer &&
777                         endContainer[0].nodeType == DOM.TEXT_NODE) {
778                         if (!endOffset) {
779                             walkerRange.setEndBefore(endContainer);
780                         } else if (endOffset >= endContainer[0].nodeValue.length) {
781                             walkerRange.setEndAfter(endContainer);
782                         } else {
783                             walkerRange.setEndAfter(endContainer);
784                             moveEnd = FALSE;
785                         }
786                     }
787 
788                     if (moveStart || moveEnd) {
789 
790                         walker = new Walker(walkerRange);
791 
792                         walker.evaluator = function (node) {
793                             return node.nodeType == ( mode == KER.SHRINK_ELEMENT ?
794                                 DOM.ELEMENT_NODE : DOM.TEXT_NODE );
795                         };
796 
797                         walker.guard = function (node, movingOut) {
798                             // Stop when we're shrink in element mode while encountering a text node.
799                             if (mode == KER.SHRINK_ELEMENT &&
800                                 node.nodeType == DOM.TEXT_NODE) {
801                                 return FALSE;
802                             }
803                             // Stop when we've already walked "through" an element.
804                             if (movingOut && node == currentElement) {
805                                 return FALSE;
806                             }
807                             if (!movingOut && node.nodeType == DOM.ELEMENT_NODE) {
808                                 currentElement = node;
809                             }
810                             return TRUE;
811                         };
812 
813                     }
814 
815                     if (moveStart) {
816                         var textStart = walker[mode == KER.SHRINK_ELEMENT ? 'lastForward' : 'next']();
817                         if (textStart) {
818                             self.setStartAt(textStart, selectContents ? KER.POSITION_AFTER_START : KER.POSITION_BEFORE_START);
819                         }
820                     }
821 
822                     if (moveEnd) {
823                         walker.reset();
824                         var textEnd = walker[mode == KER.SHRINK_ELEMENT ? 'lastBackward' : 'previous']();
825                         if (textEnd) {
826                             self.setEndAt(textEnd, selectContents ? KER.POSITION_BEFORE_END : KER.POSITION_AFTER_END);
827                         }
828                     }
829 
830                     return moveStart || moveEnd;
831                 }
832             },
833 
834             /**
835              * Create virtual bookmark by remeber its position index.
836              * @param normalized
837              */
838             createBookmark2:function (normalized) {
839 
840                 var self = this,
841                     startContainer = self.startContainer,
842                     endContainer = self.endContainer,
843                     startOffset = self.startOffset,
844                     endOffset = self.endOffset,
845                     child, previous;
846 
847                 // If there is no range then get out of here.
848                 // It happens on initial load in Safari #962 and if the editor it's
849                 // hidden also in Firefox
850                 if (!startContainer || !endContainer) {
851                     return {
852                         start:0,
853                         end:0
854                     };
855                 }
856 
857                 if (normalized) {
858                     // Find out if the start is pointing to a text node that will
859                     // be normalized.
860                     if (startContainer[0].nodeType == DOM.ELEMENT_NODE) {
861                         child = new Node(startContainer[0].childNodes[startOffset]);
862 
863                         // In this case, move the start information to that text
864                         // node.
865                         if (child && child[0] && child[0].nodeType == DOM.TEXT_NODE
866                             && startOffset > 0 && child[0].previousSibling.nodeType == DOM.TEXT_NODE) {
867                             startContainer = child;
868                             startOffset = 0;
869                         }
870 
871                     }
872 
873                     // Normalize the start.
874                     while (startContainer[0].nodeType == DOM.TEXT_NODE
875                         && ( previous = startContainer.prev(undefined, 1) )
876                         && previous[0].nodeType == DOM.TEXT_NODE) {
877                         startContainer = previous;
878                         startOffset += previous[0].nodeValue.length;
879                     }
880 
881                     // Process the end only if not normalized.
882                     if (!self.collapsed) {
883                         // Find out if the start is pointing to a text node that
884                         // will be normalized.
885                         if (endContainer[0].nodeType == DOM.ELEMENT_NODE) {
886                             child = new Node(endContainer[0].childNodes[endOffset]);
887 
888                             // In this case, move the start information to that
889                             // text node.
890                             if (child && child[0] &&
891                                 child[0].nodeType == DOM.TEXT_NODE && endOffset > 0 &&
892                                 child[0].previousSibling.nodeType == DOM.TEXT_NODE) {
893                                 endContainer = child;
894                                 endOffset = 0;
895                             }
896                         }
897 
898                         // Normalize the end.
899                         while (endContainer[0].nodeType == DOM.TEXT_NODE
900                             && ( previous = endContainer.prev(undefined, 1) )
901                             && previous[0].nodeType == DOM.TEXT_NODE) {
902                             endContainer = previous;
903                             endOffset += previous[0].nodeValue.length;
904                         }
905                     }
906                 }
907 
908                 return {
909                     start:startContainer._4e_address(normalized),
910                     end:self.collapsed ? NULL : endContainer._4e_address(normalized),
911                     startOffset:startOffset,
912                     endOffset:endOffset,
913                     normalized:normalized,
914                     is2:TRUE  // It's a createBookmark2 bookmark.
915                 };
916             },
917             /**
918              * Create bookmark by create bookmark node.
919              * @param {Boolean} [serializable]
920              */
921             createBookmark:function (serializable) {
922                 var startNode,
923                     endNode,
924                     baseId,
925                     clone,
926                     self = this,
927                     collapsed = self.collapsed;
928                 startNode = new Node("<span>", NULL, self.document);
929                 startNode.attr('_ke_bookmark', 1);
930                 startNode.css('display', 'none');
931 
932                 // For IE, it must have something inside, otherwise it may be
933                 // removed during DOM operations.
934                 startNode.html(' ');
935 
936                 if (serializable) {
937                     baseId = S.guid('ke_bm_');
938                     startNode.attr('id', baseId + 'S');
939                 }
940 
941                 // If collapsed, the endNode will not be created.
942                 if (!collapsed) {
943                     endNode = startNode.clone();
944                     endNode.html(' ');
945 
946                     if (serializable) {
947                         endNode.attr('id', baseId + 'E');
948                     }
949 
950                     clone = self.clone();
951                     clone.collapse();
952                     clone.insertNode(endNode);
953                 }
954 
955                 clone = self.clone();
956                 clone.collapse(TRUE);
957                 clone.insertNode(startNode);
958 
959                 // Update the range position.
960                 if (endNode) {
961                     self.setStartAfter(startNode);
962                     self.setEndBefore(endNode);
963                 } else {
964                     self.moveToPosition(startNode, KER.POSITION_AFTER_END);
965                 }
966 
967                 return {
968                     startNode:serializable ? baseId + 'S' : startNode,
969                     endNode:serializable ? baseId + 'E' : endNode,
970                     serializable:serializable,
971                     collapsed:collapsed
972                 };
973             },
974 
975             /**
976              * Set the start posititon and then collapse range.
977              * @param {NodeList} node
978              * @param {Number} position
979              */
980             moveToPosition:function (node, position) {
981                 var self = this;
982                 self.setStartAt(node, position);
983                 self.collapse(TRUE);
984             },
985 
986             /**
987              * Pull range out of text edge and split text node if range is in the middle of text node.
988              * @param {Boolean} ignoreStart
989              * @param {Boolean} ignoreEnd
990              */
991             trim:function (ignoreStart, ignoreEnd) {
992                 var self = this,
993                     startContainer = self.startContainer,
994                     startOffset = self.startOffset,
995                     collapsed = self.collapsed;
996 
997                 if (( !ignoreStart || collapsed ) &&
998                     startContainer[0] &&
999                     startContainer[0].nodeType == DOM.TEXT_NODE) {
1000                     // If the offset is zero, we just insert the new node before
1001                     // the start.
1002                     if (!startOffset) {
1003                         startOffset = startContainer._4e_index();
1004                         startContainer = startContainer.parent();
1005                     }
1006                     // If the offset is at the end, we'll insert it after the text
1007                     // node.
1008                     else if (startOffset >= startContainer[0].nodeValue.length) {
1009                         startOffset = startContainer._4e_index() + 1;
1010                         startContainer = startContainer.parent();
1011                     }
1012                     // In other case, we split the text node and insert the new
1013                     // node at the split point.
1014                     else {
1015                         var nextText = startContainer._4e_splitText(startOffset);
1016 
1017                         startOffset = startContainer._4e_index() + 1;
1018                         startContainer = startContainer.parent();
1019 
1020                         // Check all necessity of updating the end boundary.
1021                         if (DOM.equals(self.startContainer, self.endContainer)) {
1022                             self.setEnd(nextText, self.endOffset - self.startOffset);
1023                         } else if (DOM.equals(startContainer, self.endContainer)) {
1024                             self.endOffset += 1;
1025                         }
1026                     }
1027 
1028                     self.setStart(startContainer, startOffset);
1029 
1030                     if (collapsed) {
1031                         self.collapse(TRUE);
1032                         return;
1033                     }
1034                 }
1035 
1036                 var endContainer = self.endContainer,
1037                     endOffset = self.endOffset;
1038 
1039                 if (!( ignoreEnd || collapsed ) &&
1040                     endContainer[0] && endContainer[0].nodeType == DOM.TEXT_NODE) {
1041                     // If the offset is zero, we just insert the new node before
1042                     // the start.
1043                     if (!endOffset) {
1044                         endOffset = endContainer._4e_index();
1045                         endContainer = endContainer.parent();
1046                     }
1047                     // If the offset is at the end, we'll insert it after the text
1048                     // node.
1049                     else if (endOffset >= endContainer[0].nodeValue.length) {
1050                         endOffset = endContainer._4e_index() + 1;
1051                         endContainer = endContainer.parent();
1052                     }
1053                     // In other case, we split the text node and insert the new
1054                     // node at the split point.
1055                     else {
1056                         endContainer._4e_splitText(endOffset);
1057 
1058                         endOffset = endContainer._4e_index() + 1;
1059                         endContainer = endContainer.parent();
1060                     }
1061 
1062                     self.setEnd(endContainer, endOffset);
1063                 }
1064             },
1065             /**
1066              * Insert a new node at start position of current range
1067              * @param {NodeList} node
1068              */
1069             insertNode:function (node) {
1070                 var self = this;
1071                 self.optimizeBookmark();
1072                 self.trim(FALSE, TRUE);
1073                 var startContainer = self.startContainer,
1074                     startOffset = self.startOffset,
1075                     nextNode = startContainer[0].childNodes[startOffset] || null;
1076 
1077                 startContainer[0].insertBefore(node[0], nextNode);
1078                 // Check if we need to update the end boundary.
1079                 if (startContainer[0] == self.endContainer[0]) {
1080                     self.endOffset++;
1081                 }
1082                 // Expand the range to embrace the new node.
1083                 self.setStartBefore(node);
1084             },
1085 
1086             /**
1087              * Move range to previous saved bookmark.
1088              * @param bookmark
1089              */
1090             moveToBookmark:function (bookmark) {
1091                 var self = this,
1092                     doc = $(self.document);
1093                 if (bookmark.is2) {
1094                     // Get the start information.
1095                     var startContainer = doc._4e_getByAddress(bookmark.start, bookmark.normalized),
1096                         startOffset = bookmark.startOffset,
1097                         endContainer = bookmark.end && doc._4e_getByAddress(bookmark.end, bookmark.normalized),
1098                         endOffset = bookmark.endOffset;
1099 
1100                     // Set the start boundary.
1101                     self.setStart(startContainer, startOffset);
1102 
1103                     // Set the end boundary. If not available, collapse it.
1104                     if (endContainer) {
1105                         self.setEnd(endContainer, endOffset);
1106                     } else {
1107                         self.collapse(TRUE);
1108                     }
1109                 } else {
1110                     // Created with createBookmark().
1111                     var serializable = bookmark.serializable,
1112                         startNode = serializable ? S.one("#" + bookmark.startNode,
1113                             doc) : bookmark.startNode,
1114                         endNode = serializable ? S.one("#" + bookmark.endNode,
1115                             doc) : bookmark.endNode;
1116 
1117                     // Set the range start at the bookmark start node position.
1118                     self.setStartBefore(startNode);
1119 
1120                     // Remove it, because it may interfere in the setEndBefore call.
1121                     startNode._4e_remove();
1122 
1123                     // Set the range end at the bookmark end node position, or simply
1124                     // collapse it if it is not available.
1125                     if (endNode && endNode[0]) {
1126                         self.setEndBefore(endNode);
1127                         endNode._4e_remove();
1128                     } else {
1129                         self.collapse(TRUE);
1130                     }
1131                 }
1132             },
1133 
1134             /**
1135              * Find the node which contains current range completely.
1136              * @param {Boolean} includeSelf whether to return the only element with in range
1137              * @param {Boolean} ignoreTextNode whether to return text node's parent node.
1138              */
1139             getCommonAncestor:function (includeSelf, ignoreTextNode) {
1140                 var self = this,
1141                     start = self.startContainer,
1142                     end = self.endContainer,
1143                     ancestor;
1144 
1145                 if (start[0] == end[0]) {
1146                     if (includeSelf &&
1147                         start[0].nodeType == DOM.ELEMENT_NODE &&
1148                         self.startOffset == self.endOffset - 1) {
1149                         ancestor = new Node(start[0].childNodes[self.startOffset]);
1150                     } else {
1151                         ancestor = start;
1152                     }
1153                 } else {
1154                     ancestor = start._4e_commonAncestor(end);
1155                 }
1156 
1157                 return ignoreTextNode && ancestor[0].nodeType == DOM.TEXT_NODE
1158                     ? ancestor.parent() : ancestor;
1159             },
1160             /**
1161              * Enlarge the range as mush as possible
1162              * @param {Number} unit
1163              * @function
1164              * @example
1165              * <code>
1166              *      <div><span><span>^1</span>2^</span>x</div>
1167              *      =>
1168              *      <div>^<span><span>1</span>2</span>^x</div>
1169              * </code>
1170              */
1171             enlarge:(function () {
1172 
1173                 function enlargeElement(self, left, stop, commonAncestor) {
1174                     var container = self[left ? 'startContainer' : 'endContainer'],
1175                         enlarge,
1176                         sibling,
1177                         index = left ? 0 : 1,
1178                         commonReached = 0,
1179                         direction = left ? "previousSibling" : "nextSibling",
1180                         offset = self[left ? 'startOffset' : 'endOffset'];
1181 
1182                     if (container[0].nodeType == DOM.TEXT_NODE) {
1183                         if (left) {
1184                             // 不在字的开头,立即结束
1185                             if (offset) {
1186                                 return;
1187                             }
1188                         } else {
1189                             if (offset < container[0].nodeValue.length) {
1190                                 return
1191                             }
1192                         }
1193 
1194                         // 文字节点的兄弟
1195                         sibling = container[0][direction];
1196                         // 可能会扩展到到的容器节点
1197                         enlarge = container[0].parentNode;
1198                     } else {
1199                         // 开始节点的兄弟节点
1200                         sibling = container[0].childNodes[offset + (left ? -1 : 1)] || null;
1201                         // 可能会扩展到到的容器节点
1202                         enlarge = container[0];
1203                     }
1204 
1205                     while (enlarge) {
1206                         // 兄弟节点是否都是空节点?
1207                         while (sibling) {
1208                             if (isWhitespace(sibling) || isBookmark(sibling)) {
1209                                 sibling = sibling[direction];
1210                             } else {
1211                                 break;
1212                             }
1213                         }
1214 
1215                         // 一个兄弟节点阻止了扩展
1216                         if (sibling) {
1217                             // 如果没有超过公共祖先
1218                             if (!commonReached) {
1219                                 // 仅仅扩展到兄弟
1220                                 self[left ? 'setStartAfter' : 'setEndBefore']($(sibling));
1221                             }
1222                             return;
1223                         }
1224 
1225                         // 没有兄弟节点阻止
1226 
1227                         // 超过了公共祖先,先记下来,最终不能 partly 选择某个节点,要完全选中
1228 
1229                         enlarge = $(enlarge);
1230 
1231                         if (enlarge.nodeName() == "body") {
1232                             return;
1233                         }
1234 
1235                         if (commonReached || enlarge.equals(commonAncestor)) {
1236                             stop[index] = enlarge;
1237                             commonReached = 1;
1238                         } else {
1239                             // 扩展到容器外边
1240                             self[left ? 'setStartBefore' : 'setEndAfter'](enlarge);
1241                         }
1242 
1243                         sibling = enlarge[0][direction];
1244                         enlarge = enlarge[0].parentNode;
1245                     }
1246 
1247                 }
1248 
1249                 return function (unit) {
1250                     var self = this;
1251                     switch (unit) {
1252                         case KER.ENLARGE_ELEMENT :
1253 
1254                             if (self.collapsed) {
1255                                 return;
1256                             }
1257 
1258                             var commonAncestor = self.getCommonAncestor(),
1259                                 stop = [];
1260 
1261                             enlargeElement(self, 1, stop, commonAncestor);
1262                             enlargeElement(self, 0, stop, commonAncestor);
1263 
1264                             if (stop[0] && stop[1]) {
1265                                 var commonStop = stop[0].contains(stop[1]) ? stop[1] : stop[0];
1266                                 self.setStartBefore(commonStop);
1267                                 self.setEndAfter(commonStop);
1268                             }
1269 
1270                             break;
1271 
1272                         case KER.ENLARGE_BLOCK_CONTENTS:
1273                         case KER.ENLARGE_LIST_ITEM_CONTENTS:
1274 
1275                             // Enlarging the start boundary.
1276                             var walkerRange = new KERange(self.document);
1277                             var body = new Node(self.document.body);
1278 
1279                             walkerRange.setStartAt(body, KER.POSITION_AFTER_START);
1280                             walkerRange.setEnd(self.startContainer, self.startOffset);
1281 
1282                             var walker = new Walker(walkerRange),
1283                                 blockBoundary, // The node on which the enlarging should stop.
1284                                 tailBr, //
1285                                 defaultGuard = Walker.blockBoundary(
1286                                     ( unit == KER.ENLARGE_LIST_ITEM_CONTENTS ) ?
1287                                     { br:1 } : NULL),
1288                             // Record the encountered 'blockBoundary' for later use.
1289                                 boundaryGuard = function (node) {
1290                                     var retVal = defaultGuard(node);
1291                                     if (!retVal) {
1292                                         blockBoundary = $(node);
1293                                     }
1294                                     return retVal;
1295                                 },
1296                             // Record the encountered 'tailBr' for later use.
1297                                 tailBrGuard = function (node) {
1298                                     var retVal = boundaryGuard(node);
1299                                     if (!retVal && DOM.nodeName(node) == 'br') {
1300                                         tailBr = $(node);
1301                                     }
1302                                     return retVal;
1303                                 };
1304 
1305                             walker.guard = boundaryGuard;
1306 
1307                             enlargeable = walker.lastBackward();
1308 
1309                             // It's the body which stop the enlarging if no block boundary found.
1310                             blockBoundary = blockBoundary || body;
1311 
1312                             // Start the range at different position by comparing
1313                             // the document position of it with 'enlargeable' node.
1314                             self.setStartAt(
1315                                 blockBoundary,
1316                                 blockBoundary.nodeName() != 'br' &&
1317                                     // <table></table> <span>1234^56</span> <table></table>
1318                                     // =>
1319                                     // <table></table> ^<span>123456</span>$ <table></table>
1320 
1321                                     // <p> <span>123^456</span> </p>
1322                                     // =>
1323                                     // <p> ^<span>123456</span>$ </p>
1324                                     ( !enlargeable && self.checkStartOfBlock()
1325                                         || enlargeable && blockBoundary.contains(enlargeable) ) ?
1326                                     KER.POSITION_AFTER_START :
1327                                     KER.POSITION_AFTER_END);
1328 
1329                             // Enlarging the end boundary.
1330                             walkerRange = self.clone();
1331                             walkerRange.collapse();
1332                             walkerRange.setEndAt(body, KER.POSITION_BEFORE_END);
1333                             walker = new Walker(walkerRange);
1334 
1335                             // tailBrGuard only used for on range end.
1336                             walker.guard = ( unit == KER.ENLARGE_LIST_ITEM_CONTENTS ) ?
1337                                 tailBrGuard : boundaryGuard;
1338                             blockBoundary = NULL;
1339                             // End the range right before the block boundary node.
1340 
1341                             var enlargeable = walker.lastForward();
1342 
1343                             // It's the body which stop the enlarging if no block boundary found.
1344                             blockBoundary = blockBoundary || body;
1345 
1346                             // Start the range at different position by comparing
1347                             // the document position of it with 'enlargeable' node.
1348                             self.setEndAt(
1349                                 blockBoundary,
1350                                 ( !enlargeable && self.checkEndOfBlock()
1351                                     || enlargeable && blockBoundary.contains(enlargeable) ) ?
1352                                     KER.POSITION_BEFORE_END :
1353                                     KER.POSITION_BEFORE_START);
1354                             // We must include the <br> at the end of range if there's
1355                             // one and we're expanding list item contents
1356                             if (tailBr) {
1357                                 self.setEndAfter(tailBr);
1358                             }
1359                     }
1360                 }
1361             })(),
1362 
1363             /**
1364              * Check whether current range 's start position is at the start of a block (visible)
1365              * @return Boolean
1366              */
1367             checkStartOfBlock:function () {
1368                 var self = this,
1369                     startContainer = self.startContainer,
1370                     startOffset = self.startOffset;
1371 
1372                 // If the starting node is a text node, and non-empty before the offset,
1373                 // then we're surely not at the start of block.
1374                 if (startOffset && startContainer[0].nodeType == DOM.TEXT_NODE) {
1375                     var textBefore = S.trim(startContainer[0].nodeValue.substring(0, startOffset));
1376                     if (textBefore.length) {
1377                         return FALSE;
1378                     }
1379                 }
1380 
1381                 // Anticipate the trim() call here, so the walker will not make
1382                 // changes to the DOM, which would not get reflected into this
1383                 // range otherwise.
1384                 self.trim();
1385 
1386                 // We need to grab the block element holding the start boundary, so
1387                 // let's use an element path for it.
1388                 var path = new ElementPath(self.startContainer);
1389 
1390                 // Creates a range starting at the block start until the range start.
1391                 var walkerRange = self.clone();
1392                 walkerRange.collapse(TRUE);
1393                 walkerRange.setStartAt(path.block || path.blockLimit, KER.POSITION_AFTER_START);
1394 
1395                 var walker = new Walker(walkerRange);
1396                 walker.evaluator = getCheckStartEndBlockEvalFunction(TRUE);
1397 
1398                 return walker.checkBackward();
1399             },
1400 
1401             /**
1402              * Check whether current range 's end position is at the end of a block (visible)
1403              * @return Boolean
1404              */
1405             checkEndOfBlock:function () {
1406                 var self = this, endContainer = self.endContainer,
1407                     endOffset = self.endOffset;
1408 
1409                 // If the ending node is a text node, and non-empty after the offset,
1410                 // then we're surely not at the end of block.
1411                 if (endContainer[0].nodeType == DOM.TEXT_NODE) {
1412                     var textAfter = S.trim(endContainer[0].nodeValue.substring(endOffset));
1413                     if (textAfter.length) {
1414                         return FALSE;
1415                     }
1416                 }
1417 
1418                 // Anticipate the trim() call here, so the walker will not make
1419                 // changes to the DOM, which would not get reflected into this
1420                 // range otherwise.
1421                 self.trim();
1422 
1423                 // We need to grab the block element holding the start boundary, so
1424                 // let's use an element path for it.
1425                 var path = new ElementPath(self.endContainer);
1426 
1427                 // Creates a range starting at the block start until the range start.
1428                 var walkerRange = self.clone();
1429                 walkerRange.collapse(FALSE);
1430                 walkerRange.setEndAt(path.block || path.blockLimit, KER.POSITION_BEFORE_END);
1431 
1432                 var walker = new Walker(walkerRange);
1433                 walker.evaluator = getCheckStartEndBlockEvalFunction(FALSE);
1434 
1435                 return walker.checkForward();
1436             },
1437 
1438             /**
1439              * Check whether current range is on the inner edge of the specified element.
1440              * @param {Number} checkType The checking side.
1441              * @param {NodeList} element The target element to check.
1442              */
1443             checkBoundaryOfElement:function (element, checkType) {
1444                 var walkerRange = this.clone();
1445                 // Expand the range to element boundary.
1446                 walkerRange[ checkType == KER.START ?
1447                     'setStartAt' : 'setEndAt' ]
1448                     (element, checkType == KER.START ?
1449                         KER.POSITION_AFTER_START
1450                         : KER.POSITION_BEFORE_END);
1451 
1452                 var walker = new Walker(walkerRange);
1453 
1454                 walker.evaluator = elementBoundaryEval;
1455                 return walker[ checkType == KER.START ?
1456                     'checkBackward' : 'checkForward' ]();
1457             },
1458 
1459             /**
1460              * Get two node which are at the edge of current range.
1461              * @return {Object} Map with startNode and endNode as key/value.
1462              */
1463             getBoundaryNodes:function () {
1464                 var self = this,
1465                     startNode = self.startContainer,
1466                     endNode = self.endContainer,
1467                     startOffset = self.startOffset,
1468                     endOffset = self.endOffset,
1469                     childCount;
1470 
1471                 if (startNode[0].nodeType == DOM.ELEMENT_NODE) {
1472                     childCount = startNode[0].childNodes.length;
1473                     if (childCount > startOffset) {
1474                         startNode = $(startNode[0].childNodes[startOffset]);
1475                     } else if (childCount == 0) {
1476                         // ?? startNode
1477                         startNode = startNode._4e_previousSourceNode();
1478                     } else {
1479                         // startOffset >= childCount but childCount is not 0
1480                         // Try to take the node just after the current position.
1481                         startNode = startNode[0];
1482                         while (startNode.lastChild) {
1483                             startNode = startNode.lastChild;
1484                         }
1485 
1486                         startNode = $(startNode);
1487 
1488                         // Normally we should take the next node in DFS order. But it
1489                         // is also possible that we've already reached the end of
1490                         // document.
1491                         startNode = startNode._4e_nextSourceNode() || startNode;
1492                     }
1493                 }
1494 
1495                 if (endNode[0].nodeType == DOM.ELEMENT_NODE) {
1496                     childCount = endNode[0].childNodes.length;
1497                     if (childCount > endOffset) {
1498                         endNode = $(endNode[0].childNodes[endOffset])
1499                             // in case endOffset == 0
1500                             ._4e_previousSourceNode(TRUE);
1501                     } else if (childCount == 0) {
1502                         endNode = endNode._4e_previousSourceNode();
1503                     } else {
1504                         // endOffset > childCount but childCount is not 0
1505                         // Try to take the node just before the current position.
1506                         endNode = endNode[0];
1507                         while (endNode.lastChild)
1508                             endNode = endNode.lastChild;
1509                         endNode = $(endNode);
1510                     }
1511                 }
1512 
1513                 // Sometimes the endNode will come right before startNode for collapsed
1514                 // ranges. Fix it. (#3780)
1515                 if (startNode._4e_position(endNode) & KEP.POSITION_FOLLOWING) {
1516                     startNode = endNode;
1517                 }
1518 
1519                 return { startNode:startNode, endNode:endNode };
1520             },
1521 
1522             /**
1523              * Wrap the content in range which is block-enlarged
1524              * at the start or end of current range into a block element.
1525              * @param {Boolean} isStart Start or end of current range tobe enlarged.
1526              * @param {String} blockTag Block element's tag name.
1527              * @return {NodeList} Newly generated block element.
1528              */
1529             fixBlock:function (isStart, blockTag) {
1530                 var self = this,
1531                     bookmark = self.createBookmark(),
1532                     fixedBlock = $(self.document.createElement(blockTag));
1533                 self.collapse(isStart);
1534                 self.enlarge(KER.ENLARGE_BLOCK_CONTENTS);
1535                 fixedBlock[0].appendChild(self.extractContents());
1536                 fixedBlock._4e_trim();
1537                 if (!UA['ie']) {
1538                     fixedBlock._4e_appendBogus();
1539                 }
1540                 self.insertNode(fixedBlock);
1541                 self.moveToBookmark(bookmark);
1542                 return fixedBlock;
1543             },
1544 
1545             /**
1546              * Split current block which current range into two if current range is in the same block.
1547              * Fix block at the start and end position of range if necessary.
1548              * @param {String} blockTag Block tag if need fixBlock
1549              */
1550             splitBlock:function (blockTag) {
1551                 var self = this,
1552                     startPath = new ElementPath(self.startContainer),
1553                     endPath = new ElementPath(self.endContainer),
1554                     startBlockLimit = startPath.blockLimit,
1555                     endBlockLimit = endPath.blockLimit,
1556                     startBlock = startPath.block,
1557                     endBlock = endPath.block,
1558                     elementPath = NULL;
1559 
1560                 // Do nothing if the boundaries are in different block limits.
1561                 if (!startBlockLimit.equals(endBlockLimit)) {
1562                     return NULL;
1563                 }
1564 
1565                 // Get or fix current blocks.
1566                 if (blockTag != 'br') {
1567                     if (!startBlock) {
1568                         startBlock = self.fixBlock(TRUE, blockTag);
1569                         endBlock = new ElementPath(self.endContainer).block;
1570                     }
1571 
1572                     if (!endBlock) {
1573                         endBlock = self.fixBlock(FALSE, blockTag);
1574                     }
1575                 }
1576 
1577                 // Get the range position.
1578                 var isStartOfBlock = startBlock && self.checkStartOfBlock(),
1579                     isEndOfBlock = endBlock && self.checkEndOfBlock();
1580 
1581                 // Delete the current contents.
1582                 self.deleteContents();
1583 
1584                 if (startBlock && startBlock[0] == endBlock[0]) {
1585                     if (isEndOfBlock) {
1586                         elementPath = new ElementPath(self.startContainer);
1587                         self.moveToPosition(endBlock, KER.POSITION_AFTER_END);
1588                         endBlock = NULL;
1589                     }
1590                     else if (isStartOfBlock) {
1591                         elementPath = new ElementPath(self.startContainer);
1592                         self.moveToPosition(startBlock, KER.POSITION_BEFORE_START);
1593                         startBlock = NULL;
1594                     }
1595                     else {
1596                         endBlock = self.splitElement(startBlock);
1597                         // In Gecko, the last child node must be a bogus <br>.
1598                         // Note: bogus <br> added under <ul> or <ol> would cause
1599                         // lists to be incorrectly rendered.
1600                         if (!UA['ie'] && !S.inArray(startBlock.nodeName(), ['ul', 'ol'])) {
1601                             startBlock._4e_appendBogus();
1602                         }
1603                     }
1604                 }
1605 
1606                 return {
1607                     previousBlock:startBlock,
1608                     nextBlock:endBlock,
1609                     wasStartOfBlock:isStartOfBlock,
1610                     wasEndOfBlock:isEndOfBlock,
1611                     elementPath:elementPath
1612                 };
1613             },
1614 
1615             /**
1616              * Split toSplit element into two parts at current range's start position.
1617              * @param {NodeList} toSplit Element to split.
1618              * @return {NodeList} The second newly generated element.
1619              */
1620             splitElement:function (toSplit) {
1621                 var self = this;
1622                 if (!self.collapsed)
1623                     return NULL;
1624 
1625                 // Extract the contents of the block from the selection point to the end
1626                 // of its contents.
1627                 self.setEndAt(toSplit, KER.POSITION_BEFORE_END);
1628                 var documentFragment = self.extractContents(),
1629                 // Duplicate the element after it.
1630                     clone = toSplit.clone(FALSE);
1631 
1632                 // Place the extracted contents into the duplicated element.
1633                 clone[0].appendChild(documentFragment);
1634 
1635                 clone.insertAfter(toSplit);
1636                 self.moveToPosition(toSplit, KER.POSITION_AFTER_END);
1637                 return clone;
1638             },
1639 
1640             /**
1641              * Move the range to the depth-first start/end editing point inside
1642              * an element.
1643              * @param {NodeList} el The element to find edit point into.
1644              * @param {Boolean} [isMoveToEnd] Find start or end editing point.
1645              * Set true to find end editing point.
1646              * @return {Boolean} Whether find edit point
1647              */
1648             moveToElementEditablePosition:function (el, isMoveToEnd) {
1649                 function nextDFS(node, childOnly) {
1650                     var next;
1651 
1652                     if (node[0].nodeType == DOM.ELEMENT_NODE &&
1653                         node._4e_isEditable()) {
1654                         next = node[ isMoveToEnd ? 'last' : 'first' ](nonWhitespaceOrIsBookmark, 1);
1655                     }
1656 
1657                     if (!childOnly && !next) {
1658                         next = node[ isMoveToEnd ? 'prev' : 'next' ](nonWhitespaceOrIsBookmark, 1);
1659                     }
1660 
1661                     return next;
1662                 }
1663 
1664                 var found = 0, self = this;
1665 
1666                 while (el) {
1667                     // Stop immediately if we've found a text node.
1668                     if (el[0].nodeType == DOM.TEXT_NODE) {
1669                         self.moveToPosition(el, isMoveToEnd ?
1670                             KER.POSITION_AFTER_END :
1671                             KER.POSITION_BEFORE_START);
1672                         found = 1;
1673                         break;
1674                     }
1675 
1676                     // If an editable element is found, move inside it, but not stop the searching.
1677                     if (el[0].nodeType == DOM.ELEMENT_NODE && el._4e_isEditable()) {
1678                         self.moveToPosition(el, isMoveToEnd ?
1679                             KER.POSITION_BEFORE_END :
1680                             KER.POSITION_AFTER_START);
1681                         found = 1;
1682                     }
1683 
1684                     el = nextDFS(el, found);
1685                 }
1686 
1687                 return !!found;
1688             },
1689 
1690             /**
1691              * Set range surround current node 's content.
1692              * @param {NodeList} node
1693              */
1694             selectNodeContents:function (node) {
1695                 var self = this, domNode = node[0];
1696                 self.setStart(node, 0);
1697                 self.setEnd(node, domNode.nodeType == DOM.TEXT_NODE ?
1698                     domNode.nodeValue.length :
1699                     domNode.childNodes.length);
1700             },
1701 
1702             /*
1703              insertNodeByDtd:function (element) {
1704              var current,
1705              self = this,
1706              tmpDtd,
1707              elementName = element['nodeName'](),
1708              isBlock = dtd['$block'][ elementName ];
1709              self.deleteContents();
1710              if (isBlock) {
1711              while (( current = self.getCommonAncestor(FALSE, TRUE) ) &&
1712              ( tmpDtd = dtd[ current.nodeName() ] ) &&
1713              !( tmpDtd && tmpDtd [ elementName ] )) {
1714              // Split up inline elements.
1715              if (current.nodeName() in dtd["span"]) {
1716              self.splitElement(current);
1717              }
1718              // If we're in an empty block which indicate a new paragraph,
1719              // simply replace it with the inserting block.(#3664)
1720              else if (self.checkStartOfBlock() && self.checkEndOfBlock()) {
1721              self.setStartBefore(current);
1722              self.collapse(TRUE);
1723              current.remove();
1724              }
1725              else {
1726              self.splitBlock(undefined);
1727              }
1728              }
1729              }
1730              // Insert the new node.
1731              self.insertNode(element);
1732              },*/
1733 
1734             /**
1735              * Insert node by dtd.(not invalidate dtd convention)
1736              * @param {NodeList} element
1737              */
1738             insertNodeByDtd:function (element) {
1739                 var current,
1740                     self = this,
1741                     tmpDtd,
1742                     last,
1743                     elementName = element['nodeName'](),
1744                     isBlock = dtd['$block'][ elementName ];
1745                 self.deleteContents();
1746                 if (isBlock) {
1747                     current = self.getCommonAncestor(FALSE, TRUE);
1748                     while (( tmpDtd = dtd[ current.nodeName() ] ) &&
1749                         !( tmpDtd && tmpDtd [ elementName ] )) {
1750                         var parent = current.parent();
1751                         // If we're in an empty block which indicate a new paragraph,
1752                         // simply replace it with the inserting block.(#3664)
1753                         if (self.checkStartOfBlock() && self.checkEndOfBlock()) {
1754                             self.setStartBefore(current);
1755                             self.collapse(TRUE);
1756                             current.remove();
1757                         } else {
1758                             last = current;
1759                         }
1760                         current = parent;
1761 
1762                     }
1763                     if (last) {
1764                         self.splitElement(last);
1765                     }
1766                 }
1767                 // Insert the new node.
1768                 self.insertNode(element);
1769             }
1770         });
1771 
1772     Utils.injectDom({
1773         _4e_breakParent:function (el, parent) {
1774             parent = $(parent);
1775             el = $(el);
1776 
1777             var KERange = Editor.Range,
1778                 docFrag,
1779                 range = new KERange(el[0].ownerDocument);
1780 
1781             // We'll be extracting part of this element, so let's use our
1782             // range to get the correct piece.
1783             range.setStartAfter(el);
1784             range.setEndAfter(parent);
1785 
1786             // Extract it.
1787             docFrag = range.extractContents();
1788 
1789             // Move the element outside the broken element.
1790             range.insertNode(el.remove());
1791 
1792             // Re-insert the extracted piece after the element.
1793             el.after(docFrag);
1794         }
1795     });
1796 
1797     Editor.Range = KERange;
1798 
1799     return KERange;
1800 }, {
1801     requires:['./base', './utils', './walker', './elementPath', './dom']
1802 });
1803