JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.6.1
[ckeditor.git] / _source / core / dom / node.js
1 /*\r
2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.\r
3 For licensing, see LICENSE.html or http://ckeditor.com/license\r
4 */\r
5 \r
6 /**\r
7  * @fileOverview Defines the {@link CKEDITOR.dom.node} class which is the base\r
8  *              class for classes that represent DOM nodes.\r
9  */\r
10 \r
11 /**\r
12  * Base class for classes representing DOM nodes. This constructor may return\r
13  * an instance of a class that inherits from this class, like\r
14  * {@link CKEDITOR.dom.element} or {@link CKEDITOR.dom.text}.\r
15  * @augments CKEDITOR.dom.domObject\r
16  * @param {Object} domNode A native DOM node.\r
17  * @constructor\r
18  * @see CKEDITOR.dom.element\r
19  * @see CKEDITOR.dom.text\r
20  * @example\r
21  */\r
22 CKEDITOR.dom.node = function( domNode )\r
23 {\r
24         if ( domNode )\r
25         {\r
26                 switch ( domNode.nodeType )\r
27                 {\r
28                         // Safari don't consider document as element node type. (#3389)\r
29                         case CKEDITOR.NODE_DOCUMENT :\r
30                                 return new CKEDITOR.dom.document( domNode );\r
31 \r
32                         case CKEDITOR.NODE_ELEMENT :\r
33                                 return new CKEDITOR.dom.element( domNode );\r
34 \r
35                         case CKEDITOR.NODE_TEXT :\r
36                                 return new CKEDITOR.dom.text( domNode );\r
37                 }\r
38 \r
39                 // Call the base constructor.\r
40                 CKEDITOR.dom.domObject.call( this, domNode );\r
41         }\r
42 \r
43         return this;\r
44 };\r
45 \r
46 CKEDITOR.dom.node.prototype = new CKEDITOR.dom.domObject();\r
47 \r
48 /**\r
49  * Element node type.\r
50  * @constant\r
51  * @example\r
52  */\r
53 CKEDITOR.NODE_ELEMENT = 1;\r
54 \r
55 /**\r
56  * Document node type.\r
57  * @constant\r
58  * @example\r
59  */\r
60 CKEDITOR.NODE_DOCUMENT = 9;\r
61 \r
62 /**\r
63  * Text node type.\r
64  * @constant\r
65  * @example\r
66  */\r
67 CKEDITOR.NODE_TEXT = 3;\r
68 \r
69 /**\r
70  * Comment node type.\r
71  * @constant\r
72  * @example\r
73  */\r
74 CKEDITOR.NODE_COMMENT = 8;\r
75 \r
76 CKEDITOR.NODE_DOCUMENT_FRAGMENT = 11;\r
77 \r
78 CKEDITOR.POSITION_IDENTICAL = 0;\r
79 CKEDITOR.POSITION_DISCONNECTED = 1;\r
80 CKEDITOR.POSITION_FOLLOWING = 2;\r
81 CKEDITOR.POSITION_PRECEDING = 4;\r
82 CKEDITOR.POSITION_IS_CONTAINED = 8;\r
83 CKEDITOR.POSITION_CONTAINS = 16;\r
84 \r
85 CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype,\r
86         /** @lends CKEDITOR.dom.node.prototype */\r
87         {\r
88                 /**\r
89                  * Makes this node a child of another element.\r
90                  * @param {CKEDITOR.dom.element} element The target element to which\r
91                  *              this node will be appended.\r
92                  * @returns {CKEDITOR.dom.element} The target element.\r
93                  * @example\r
94                  * var p = new CKEDITOR.dom.element( 'p' );\r
95                  * var strong = new CKEDITOR.dom.element( 'strong' );\r
96                  * strong.appendTo( p );\r
97                  *\r
98                  * // result: "<p><strong></strong></p>"\r
99                  */\r
100                 appendTo : function( element, toStart )\r
101                 {\r
102                         element.append( this, toStart );\r
103                         return element;\r
104                 },\r
105 \r
106                 clone : function( includeChildren, cloneId )\r
107                 {\r
108                         var $clone = this.$.cloneNode( includeChildren );\r
109 \r
110                         var removeIds = function( node )\r
111                         {\r
112                                 if ( node.nodeType != CKEDITOR.NODE_ELEMENT )\r
113                                         return;\r
114 \r
115                                 if ( !cloneId )\r
116                                         node.removeAttribute( 'id', false );\r
117                                 node.removeAttribute( 'data-cke-expando', false );\r
118 \r
119                                 if ( includeChildren )\r
120                                 {\r
121                                         var childs = node.childNodes;\r
122                                         for ( var i=0; i < childs.length; i++ )\r
123                                                 removeIds( childs[ i ] );\r
124                                 }\r
125                         };\r
126 \r
127                         // The "id" attribute should never be cloned to avoid duplication.\r
128                         removeIds( $clone );\r
129 \r
130                         return new CKEDITOR.dom.node( $clone );\r
131                 },\r
132 \r
133                 hasPrevious : function()\r
134                 {\r
135                         return !!this.$.previousSibling;\r
136                 },\r
137 \r
138                 hasNext : function()\r
139                 {\r
140                         return !!this.$.nextSibling;\r
141                 },\r
142 \r
143                 /**\r
144                  * Inserts this element after a node.\r
145                  * @param {CKEDITOR.dom.node} node The node that will precede this element.\r
146                  * @returns {CKEDITOR.dom.node} The node preceding this one after\r
147                  *              insertion.\r
148                  * @example\r
149                  * var em = new CKEDITOR.dom.element( 'em' );\r
150                  * var strong = new CKEDITOR.dom.element( 'strong' );\r
151                  * strong.insertAfter( em );\r
152                  *\r
153                  * // result: "&lt;em&gt;&lt;/em&gt;&lt;strong&gt;&lt;/strong&gt;"\r
154                  */\r
155                 insertAfter : function( node )\r
156                 {\r
157                         node.$.parentNode.insertBefore( this.$, node.$.nextSibling );\r
158                         return node;\r
159                 },\r
160 \r
161                 /**\r
162                  * Inserts this element before a node.\r
163                  * @param {CKEDITOR.dom.node} node The node that will succeed this element.\r
164                  * @returns {CKEDITOR.dom.node} The node being inserted.\r
165                  * @example\r
166                  * var em = new CKEDITOR.dom.element( 'em' );\r
167                  * var strong = new CKEDITOR.dom.element( 'strong' );\r
168                  * strong.insertBefore( em );\r
169                  *\r
170                  * // result: "&lt;strong&gt;&lt;/strong&gt;&lt;em&gt;&lt;/em&gt;"\r
171                  */\r
172                 insertBefore : function( node )\r
173                 {\r
174                         node.$.parentNode.insertBefore( this.$, node.$ );\r
175                         return node;\r
176                 },\r
177 \r
178                 insertBeforeMe : function( node )\r
179                 {\r
180                         this.$.parentNode.insertBefore( node.$, this.$ );\r
181                         return node;\r
182                 },\r
183 \r
184                 /**\r
185                  * Retrieves a uniquely identifiable tree address for this node.\r
186                  * The tree address returned is an array of integers, with each integer\r
187                  * indicating a child index of a DOM node, starting from\r
188                  * <code>document.documentElement</code>.\r
189                  *\r
190                  * For example, assuming <code>&lt;body&gt;</code> is the second child\r
191                  * of <code>&lt;html&gt;</code> (<code>&lt;head&gt;</code> being the first),\r
192                  * and we would like to address the third child under the\r
193                  * fourth child of <code>&lt;body&gt;</code>, the tree address returned would be:\r
194                  * [1, 3, 2]\r
195                  *\r
196                  * The tree address cannot be used for finding back the DOM tree node once\r
197                  * the DOM tree structure has been modified.\r
198                  */\r
199                 getAddress : function( normalized )\r
200                 {\r
201                         var address = [];\r
202                         var $documentElement = this.getDocument().$.documentElement;\r
203                         var node = this.$;\r
204 \r
205                         while ( node && node != $documentElement )\r
206                         {\r
207                                 var parentNode = node.parentNode;\r
208 \r
209                                 if ( parentNode )\r
210                                 {\r
211                                         // Get the node index. For performance, call getIndex\r
212                                         // directly, instead of creating a new node object.\r
213                                         address.unshift( this.getIndex.call( { $ : node }, normalized ) );\r
214                                 }\r
215 \r
216                                 node = parentNode;\r
217                         }\r
218 \r
219                         return address;\r
220                 },\r
221 \r
222                 /**\r
223                  * Gets the document containing this element.\r
224                  * @returns {CKEDITOR.dom.document} The document.\r
225                  * @example\r
226                  * var element = CKEDITOR.document.getById( 'example' );\r
227                  * alert( <strong>element.getDocument().equals( CKEDITOR.document )</strong> );  // "true"\r
228                  */\r
229                 getDocument : function()\r
230                 {\r
231                         return new CKEDITOR.dom.document( this.$.ownerDocument || this.$.parentNode.ownerDocument );\r
232                 },\r
233 \r
234                 getIndex : function( normalized )\r
235                 {\r
236                         // Attention: getAddress depends on this.$\r
237 \r
238                         var current = this.$,\r
239                                 index = 0;\r
240 \r
241                         while ( ( current = current.previousSibling ) )\r
242                         {\r
243                                 // When normalizing, do not count it if this is an\r
244                                 // empty text node or if it's a text node following another one.\r
245                                 if ( normalized && current.nodeType == 3 &&\r
246                                          ( !current.nodeValue.length ||\r
247                                            ( current.previousSibling && current.previousSibling.nodeType == 3 ) ) )\r
248                                 {\r
249                                         continue;\r
250                                 }\r
251 \r
252                                 index++;\r
253                         }\r
254 \r
255                         return index;\r
256                 },\r
257 \r
258                 getNextSourceNode : function( startFromSibling, nodeType, guard )\r
259                 {\r
260                         // If "guard" is a node, transform it in a function.\r
261                         if ( guard && !guard.call )\r
262                         {\r
263                                 var guardNode = guard;\r
264                                 guard = function( node )\r
265                                 {\r
266                                         return !node.equals( guardNode );\r
267                                 };\r
268                         }\r
269 \r
270                         var node = ( !startFromSibling && this.getFirst && this.getFirst() ),\r
271                                 parent;\r
272 \r
273                         // Guarding when we're skipping the current element( no children or 'startFromSibling' ).\r
274                         // send the 'moving out' signal even we don't actually dive into.\r
275                         if ( !node )\r
276                         {\r
277                                 if ( this.type == CKEDITOR.NODE_ELEMENT && guard && guard( this, true ) === false )\r
278                                         return null;\r
279                                 node = this.getNext();\r
280                         }\r
281 \r
282                         while ( !node && ( parent = ( parent || this ).getParent() ) )\r
283                         {\r
284                                 // The guard check sends the "true" paramenter to indicate that\r
285                                 // we are moving "out" of the element.\r
286                                 if ( guard && guard( parent, true ) === false )\r
287                                         return null;\r
288 \r
289                                 node = parent.getNext();\r
290                         }\r
291 \r
292                         if ( !node )\r
293                                 return null;\r
294 \r
295                         if ( guard && guard( node ) === false )\r
296                                 return null;\r
297 \r
298                         if ( nodeType && nodeType != node.type )\r
299                                 return node.getNextSourceNode( false, nodeType, guard );\r
300 \r
301                         return node;\r
302                 },\r
303 \r
304                 getPreviousSourceNode : function( startFromSibling, nodeType, guard )\r
305                 {\r
306                         if ( guard && !guard.call )\r
307                         {\r
308                                 var guardNode = guard;\r
309                                 guard = function( node )\r
310                                 {\r
311                                         return !node.equals( guardNode );\r
312                                 };\r
313                         }\r
314 \r
315                         var node = ( !startFromSibling && this.getLast && this.getLast() ),\r
316                                 parent;\r
317 \r
318                         // Guarding when we're skipping the current element( no children or 'startFromSibling' ).\r
319                         // send the 'moving out' signal even we don't actually dive into.\r
320                         if ( !node )\r
321                         {\r
322                                 if ( this.type == CKEDITOR.NODE_ELEMENT && guard && guard( this, true ) === false )\r
323                                         return null;\r
324                                 node = this.getPrevious();\r
325                         }\r
326 \r
327                         while ( !node && ( parent = ( parent || this ).getParent() ) )\r
328                         {\r
329                                 // The guard check sends the "true" paramenter to indicate that\r
330                                 // we are moving "out" of the element.\r
331                                 if ( guard && guard( parent, true ) === false )\r
332                                         return null;\r
333 \r
334                                 node = parent.getPrevious();\r
335                         }\r
336 \r
337                         if ( !node )\r
338                                 return null;\r
339 \r
340                         if ( guard && guard( node ) === false )\r
341                                 return null;\r
342 \r
343                         if ( nodeType && node.type != nodeType )\r
344                                 return node.getPreviousSourceNode( false, nodeType, guard );\r
345 \r
346                         return node;\r
347                 },\r
348 \r
349                 getPrevious : function( evaluator )\r
350                 {\r
351                         var previous = this.$, retval;\r
352                         do\r
353                         {\r
354                                 previous = previous.previousSibling;\r
355                                 retval = previous && new CKEDITOR.dom.node( previous );\r
356                         }\r
357                         while ( retval && evaluator && !evaluator( retval ) )\r
358                         return retval;\r
359                 },\r
360 \r
361                 /**\r
362                  * Gets the node that follows this element in its parent's child list.\r
363                  * @param {Function} evaluator Filtering the result node.\r
364                  * @returns {CKEDITOR.dom.node} The next node or null if not available.\r
365                  * @example\r
366                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;&lt;b&gt;Example&lt;/b&gt; &lt;i&gt;next&lt;/i&gt;&lt;/div&gt;' );\r
367                  * var first = <strong>element.getFirst().getNext()</strong>;\r
368                  * alert( first.getName() );  // "i"\r
369                  */\r
370                 getNext : function( evaluator )\r
371                 {\r
372                         var next = this.$, retval;\r
373                         do\r
374                         {\r
375                                 next = next.nextSibling;\r
376                                 retval = next && new CKEDITOR.dom.node( next );\r
377                         }\r
378                         while ( retval && evaluator && !evaluator( retval ) )\r
379                         return retval;\r
380                 },\r
381 \r
382                 /**\r
383                  * Gets the parent element for this node.\r
384                  * @returns {CKEDITOR.dom.element} The parent element.\r
385                  * @example\r
386                  * var node = editor.document.getBody().getFirst();\r
387                  * var parent = node.<strong>getParent()</strong>;\r
388                  * alert( node.getName() );  // "body"\r
389                  */\r
390                 getParent : function()\r
391                 {\r
392                         var parent = this.$.parentNode;\r
393                         return ( parent && parent.nodeType == 1 ) ? new CKEDITOR.dom.node( parent ) : null;\r
394                 },\r
395 \r
396                 getParents : function( closerFirst )\r
397                 {\r
398                         var node = this;\r
399                         var parents = [];\r
400 \r
401                         do\r
402                         {\r
403                                 parents[  closerFirst ? 'push' : 'unshift' ]( node );\r
404                         }\r
405                         while ( ( node = node.getParent() ) )\r
406 \r
407                         return parents;\r
408                 },\r
409 \r
410                 getCommonAncestor : function( node )\r
411                 {\r
412                         if ( node.equals( this ) )\r
413                                 return this;\r
414 \r
415                         if ( node.contains && node.contains( this ) )\r
416                                 return node;\r
417 \r
418                         var start = this.contains ? this : this.getParent();\r
419 \r
420                         do\r
421                         {\r
422                                 if ( start.contains( node ) )\r
423                                         return start;\r
424                         }\r
425                         while ( ( start = start.getParent() ) );\r
426 \r
427                         return null;\r
428                 },\r
429 \r
430                 getPosition : function( otherNode )\r
431                 {\r
432                         var $ = this.$;\r
433                         var $other = otherNode.$;\r
434 \r
435                         if ( $.compareDocumentPosition )\r
436                                 return $.compareDocumentPosition( $other );\r
437 \r
438                         // IE and Safari have no support for compareDocumentPosition.\r
439 \r
440                         if ( $ == $other )\r
441                                 return CKEDITOR.POSITION_IDENTICAL;\r
442 \r
443                         // Only element nodes support contains and sourceIndex.\r
444                         if ( this.type == CKEDITOR.NODE_ELEMENT && otherNode.type == CKEDITOR.NODE_ELEMENT )\r
445                         {\r
446                                 if ( $.contains )\r
447                                 {\r
448                                         if ( $.contains( $other ) )\r
449                                                 return CKEDITOR.POSITION_CONTAINS + CKEDITOR.POSITION_PRECEDING;\r
450 \r
451                                         if ( $other.contains( $ ) )\r
452                                                 return CKEDITOR.POSITION_IS_CONTAINED + CKEDITOR.POSITION_FOLLOWING;\r
453                                 }\r
454 \r
455                                 if ( 'sourceIndex' in $ )\r
456                                 {\r
457                                         return ( $.sourceIndex < 0 || $other.sourceIndex < 0 ) ? CKEDITOR.POSITION_DISCONNECTED :\r
458                                                 ( $.sourceIndex < $other.sourceIndex ) ? CKEDITOR.POSITION_PRECEDING :\r
459                                                 CKEDITOR.POSITION_FOLLOWING;\r
460                                 }\r
461                         }\r
462 \r
463                         // For nodes that don't support compareDocumentPosition, contains\r
464                         // or sourceIndex, their "address" is compared.\r
465 \r
466                         var addressOfThis = this.getAddress(),\r
467                                 addressOfOther = otherNode.getAddress(),\r
468                                 minLevel = Math.min( addressOfThis.length, addressOfOther.length );\r
469 \r
470                                 // Determinate preceed/follow relationship.\r
471                                 for ( var i = 0 ; i <= minLevel - 1 ; i++ )\r
472                                 {\r
473                                         if ( addressOfThis[ i ] != addressOfOther[ i ] )\r
474                                         {\r
475                                                 if ( i < minLevel )\r
476                                                 {\r
477                                                         return addressOfThis[ i ] < addressOfOther[ i ] ?\r
478                                                             CKEDITOR.POSITION_PRECEDING : CKEDITOR.POSITION_FOLLOWING;\r
479                                                 }\r
480                                                 break;\r
481                                         }\r
482                                 }\r
483 \r
484                                 // Determinate contains/contained relationship.\r
485                                 return ( addressOfThis.length < addressOfOther.length ) ?\r
486                                                         CKEDITOR.POSITION_CONTAINS + CKEDITOR.POSITION_PRECEDING :\r
487                                                         CKEDITOR.POSITION_IS_CONTAINED + CKEDITOR.POSITION_FOLLOWING;\r
488                 },\r
489 \r
490                 /**\r
491                  * Gets the closest ancestor node of this node, specified by its name.\r
492                  * @param {String} reference The name of the ancestor node to search or\r
493                  *              an object with the node names to search for.\r
494                  * @param {Boolean} [includeSelf] Whether to include the current\r
495                  *              node in the search.\r
496                  * @returns {CKEDITOR.dom.node} The located ancestor node or null if not found.\r
497                  * @since 3.6.1\r
498                  * @example\r
499                  * // Suppose we have the following HTML structure:\r
500                  * // &lt;div id="outer"&gt;&lt;div id="inner"&gt;&lt;p&gt;&lt;b&gt;Some text&lt;/b&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;\r
501                  * // If node == &lt;b&gt;\r
502                  * ascendant = node.getAscendant( 'div' );      // ascendant == &lt;div id="inner"&gt\r
503                  * ascendant = node.getAscendant( 'b' );        // ascendant == null\r
504                  * ascendant = node.getAscendant( 'b', true );  // ascendant == &lt;b&gt;\r
505                  * ascendant = node.getAscendant( { div: 1, p: 1} );      // Searches for the first 'div' or 'p': ascendant == &lt;div id="inner"&gt\r
506                  */\r
507                 getAscendant : function( reference, includeSelf )\r
508                 {\r
509                         var $ = this.$,\r
510                                 name;\r
511 \r
512                         if ( !includeSelf )\r
513                                 $ = $.parentNode;\r
514 \r
515                         while ( $ )\r
516                         {\r
517                                 if ( $.nodeName && ( name = $.nodeName.toLowerCase(), ( typeof reference == 'string' ? name == reference : name in reference ) ) )\r
518                                         return new CKEDITOR.dom.node( $ );\r
519 \r
520                                 $ = $.parentNode;\r
521                         }\r
522                         return null;\r
523                 },\r
524 \r
525                 hasAscendant : function( name, includeSelf )\r
526                 {\r
527                         var $ = this.$;\r
528 \r
529                         if ( !includeSelf )\r
530                                 $ = $.parentNode;\r
531 \r
532                         while ( $ )\r
533                         {\r
534                                 if ( $.nodeName && $.nodeName.toLowerCase() == name )\r
535                                         return true;\r
536 \r
537                                 $ = $.parentNode;\r
538                         }\r
539                         return false;\r
540                 },\r
541 \r
542                 move : function( target, toStart )\r
543                 {\r
544                         target.append( this.remove(), toStart );\r
545                 },\r
546 \r
547                 /**\r
548                  * Removes this node from the document DOM.\r
549                  * @param {Boolean} [preserveChildren] Indicates that the children\r
550                  *              elements must remain in the document, removing only the outer\r
551                  *              tags.\r
552                  * @example\r
553                  * var element = CKEDITOR.dom.element.getById( 'MyElement' );\r
554                  * <strong>element.remove()</strong>;\r
555                  */\r
556                 remove : function( preserveChildren )\r
557                 {\r
558                         var $ = this.$;\r
559                         var parent = $.parentNode;\r
560 \r
561                         if ( parent )\r
562                         {\r
563                                 if ( preserveChildren )\r
564                                 {\r
565                                         // Move all children before the node.\r
566                                         for ( var child ; ( child = $.firstChild ) ; )\r
567                                         {\r
568                                                 parent.insertBefore( $.removeChild( child ), $ );\r
569                                         }\r
570                                 }\r
571 \r
572                                 parent.removeChild( $ );\r
573                         }\r
574 \r
575                         return this;\r
576                 },\r
577 \r
578                 replace : function( nodeToReplace )\r
579                 {\r
580                         this.insertBefore( nodeToReplace );\r
581                         nodeToReplace.remove();\r
582                 },\r
583 \r
584                 trim : function()\r
585                 {\r
586                         this.ltrim();\r
587                         this.rtrim();\r
588                 },\r
589 \r
590                 ltrim : function()\r
591                 {\r
592                         var child;\r
593                         while ( this.getFirst && ( child = this.getFirst() ) )\r
594                         {\r
595                                 if ( child.type == CKEDITOR.NODE_TEXT )\r
596                                 {\r
597                                         var trimmed = CKEDITOR.tools.ltrim( child.getText() ),\r
598                                                 originalLength = child.getLength();\r
599 \r
600                                         if ( !trimmed )\r
601                                         {\r
602                                                 child.remove();\r
603                                                 continue;\r
604                                         }\r
605                                         else if ( trimmed.length < originalLength )\r
606                                         {\r
607                                                 child.split( originalLength - trimmed.length );\r
608 \r
609                                                 // IE BUG: child.remove() may raise JavaScript errors here. (#81)\r
610                                                 this.$.removeChild( this.$.firstChild );\r
611                                         }\r
612                                 }\r
613                                 break;\r
614                         }\r
615                 },\r
616 \r
617                 rtrim : function()\r
618                 {\r
619                         var child;\r
620                         while ( this.getLast && ( child = this.getLast() ) )\r
621                         {\r
622                                 if ( child.type == CKEDITOR.NODE_TEXT )\r
623                                 {\r
624                                         var trimmed = CKEDITOR.tools.rtrim( child.getText() ),\r
625                                                 originalLength = child.getLength();\r
626 \r
627                                         if ( !trimmed )\r
628                                         {\r
629                                                 child.remove();\r
630                                                 continue;\r
631                                         }\r
632                                         else if ( trimmed.length < originalLength )\r
633                                         {\r
634                                                 child.split( trimmed.length );\r
635 \r
636                                                 // IE BUG: child.getNext().remove() may raise JavaScript errors here.\r
637                                                 // (#81)\r
638                                                 this.$.lastChild.parentNode.removeChild( this.$.lastChild );\r
639                                         }\r
640                                 }\r
641                                 break;\r
642                         }\r
643 \r
644                         if ( !CKEDITOR.env.ie && !CKEDITOR.env.opera )\r
645                         {\r
646                                 child = this.$.lastChild;\r
647 \r
648                                 if ( child && child.type == 1 && child.nodeName.toLowerCase() == 'br' )\r
649                                 {\r
650                                         // Use "eChildNode.parentNode" instead of "node" to avoid IE bug (#324).\r
651                                         child.parentNode.removeChild( child ) ;\r
652                                 }\r
653                         }\r
654                 },\r
655 \r
656                 /**\r
657                  * Checks if this node is read-only (should not be changed). Additionally\r
658                  * it returns the element that defines the read-only state of this node\r
659                  * (if present). It may be the node itself or any of its parent\r
660                  * nodes.\r
661                  * @returns {CKEDITOR.dom.element|Boolean} An element containing\r
662                  *              read-only attributes or "false" if none is found.\r
663                  * @since 3.5\r
664                  * @example\r
665                  * // For the following HTML:\r
666                  * // &lt;div contenteditable="false"&gt;Some &lt;b&gt;text&lt;/b&gt;&lt;/div&gt;\r
667                  *\r
668                  * // If "ele" is the above &lt;div&gt;\r
669                  * ele.isReadOnly();  // the &lt;div&gt; element\r
670                  *\r
671                  * // If "ele" is the above &lt;b&gt;\r
672                  * ele.isReadOnly();  // the &lt;div&gt; element\r
673                  */\r
674                 isReadOnly : function()\r
675                 {\r
676                         var current = this;\r
677                         while( current )\r
678                         {\r
679                                 if ( current.type == CKEDITOR.NODE_ELEMENT )\r
680                                 {\r
681                                         if ( current.is( 'body' ) || !!current.data( 'cke-editable' ) )\r
682                                                 break;\r
683 \r
684                                         if ( current.getAttribute( 'contentEditable' ) == 'false' )\r
685                                                 return current;\r
686                                         else if ( current.getAttribute( 'contentEditable' ) == 'true' )\r
687                                                 break;\r
688                                 }\r
689                                 current = current.getParent();\r
690                         }\r
691 \r
692                         return false;\r
693                 }\r
694         }\r
695 );\r