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