initial commit
[namibia] / public / scripts / ckeditor / _source / core / dom / element.js
1 /*
2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
4 */
5
6 /**
7  * @fileOverview Defines the {@link CKEDITOR.dom.element} class, which
8  *              represents a DOM element.
9  */
10
11 /**
12  * Represents a DOM element.
13  * @constructor
14  * @augments CKEDITOR.dom.node
15  * @param {Object|String} element A native DOM element or the element name for
16  *              new elements.
17  * @param {CKEDITOR.dom.document} [ownerDocument] The document that will contain
18  *              the element in case of element creation.
19  * @example
20  * // Create a new <span> element.
21  * var element = new CKEDITOR.dom.element( 'span' );
22  * @example
23  * // Create an element based on a native DOM element.
24  * var element = new CKEDITOR.dom.element( document.getElementById( 'myId' ) );
25  */
26 CKEDITOR.dom.element = function( element, ownerDocument )
27 {
28         if ( typeof element == 'string' )
29                 element = ( ownerDocument ? ownerDocument.$ : document ).createElement( element );
30
31         // Call the base constructor (we must not call CKEDITOR.dom.node).
32         CKEDITOR.dom.domObject.call( this, element );
33 };
34
35 // PACKAGER_RENAME( CKEDITOR.dom.element )
36
37 /**
38  * The the {@link CKEDITOR.dom.element} representing and element. If the
39  * element is a native DOM element, it will be transformed into a valid
40  * CKEDITOR.dom.element object.
41  * @returns {CKEDITOR.dom.element} The transformed element.
42  * @example
43  * var element = new CKEDITOR.dom.element( 'span' );
44  * alert( element == <b>CKEDITOR.dom.element.get( element )</b> );  "true"
45  * @example
46  * var element = document.getElementById( 'myElement' );
47  * alert( <b>CKEDITOR.dom.element.get( element )</b>.getName() );  e.g. "p"
48  */
49 CKEDITOR.dom.element.get = function( element )
50 {
51         return element && ( element.$ ? element : new CKEDITOR.dom.element( element ) );
52 };
53
54 CKEDITOR.dom.element.prototype = new CKEDITOR.dom.node();
55
56 /**
57  * Creates an instance of the {@link CKEDITOR.dom.element} class based on the
58  * HTML representation of an element.
59  * @param {String} html The element HTML. It should define only one element in
60  *              the "root" level. The "root" element can have child nodes, but not
61  *              siblings.
62  * @returns {CKEDITOR.dom.element} The element instance.
63  * @example
64  * var element = <b>CKEDITOR.dom.element.createFromHtml( '&lt;strong class="anyclass"&gt;My element&lt;/strong&gt;' )</b>;
65  * alert( element.getName() );  // "strong"
66  */
67 CKEDITOR.dom.element.createFromHtml = function( html, ownerDocument )
68 {
69         var temp = new CKEDITOR.dom.element( 'div', ownerDocument );
70         temp.setHtml( html );
71
72         // When returning the node, remove it from its parent to detach it.
73         return temp.getFirst().remove();
74 };
75
76 CKEDITOR.dom.element.setMarker = function( database, element, name, value )
77 {
78         var id = element.getCustomData( 'list_marker_id' ) ||
79                         ( element.setCustomData( 'list_marker_id', CKEDITOR.tools.getNextNumber() ).getCustomData( 'list_marker_id' ) ),
80                 markerNames = element.getCustomData( 'list_marker_names' ) ||
81                         ( element.setCustomData( 'list_marker_names', {} ).getCustomData( 'list_marker_names' ) );
82         database[id] = element;
83         markerNames[name] = 1;
84
85         return element.setCustomData( name, value );
86 };
87
88 CKEDITOR.dom.element.clearAllMarkers = function( database )
89 {
90         for ( var i in database )
91                 CKEDITOR.dom.element.clearMarkers( database, database[i], 1 );
92 };
93
94 CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatabase )
95 {
96         var names = element.getCustomData( 'list_marker_names' ),
97                 id = element.getCustomData( 'list_marker_id' );
98         for ( var i in names )
99                 element.removeCustomData( i );
100         element.removeCustomData( 'list_marker_names' );
101         if ( removeFromDatabase )
102         {
103                 element.removeCustomData( 'list_marker_id' );
104                 delete database[id];
105         }
106 };
107
108 CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
109         /** @lends CKEDITOR.dom.element.prototype */
110         {
111                 /**
112                  * The node type. This is a constant value set to
113                  * {@link CKEDITOR.NODE_ELEMENT}.
114                  * @type Number
115                  * @example
116                  */
117                 type : CKEDITOR.NODE_ELEMENT,
118
119                 /**
120                  * Adds a CSS class to the element. It appends the class to the
121                  * already existing names.
122                  * @param {String} className The name of the class to be added.
123                  * @example
124                  * var element = new CKEDITOR.dom.element( 'div' );
125                  * element.addClass( 'classA' );  // &lt;div class="classA"&gt;
126                  * element.addClass( 'classB' );  // &lt;div class="classA classB"&gt;
127                  * element.addClass( 'classA' );  // &lt;div class="classA classB"&gt;
128                  */
129                 addClass : function( className )
130                 {
131                         var c = this.$.className;
132                         if ( c )
133                         {
134                                 var regex = new RegExp( '(?:^|\\s)' + className + '(?:\\s|$)', '' );
135                                 if ( !regex.test( c ) )
136                                         c += ' ' + className;
137                         }
138                         this.$.className = c || className;
139                 },
140
141                 /**
142                  * Removes a CSS class name from the elements classes. Other classes
143                  * remain untouched.
144                  * @param {String} className The name of the class to remove.
145                  * @example
146                  * var element = new CKEDITOR.dom.element( 'div' );
147                  * element.addClass( 'classA' );  // &lt;div class="classA"&gt;
148                  * element.addClass( 'classB' );  // &lt;div class="classA classB"&gt;
149                  * element.removeClass( 'classA' );  // &lt;div class="classB"&gt;
150                  * element.removeClass( 'classB' );  // &lt;div&gt;
151                  */
152                 removeClass : function( className )
153                 {
154                         var c = this.getAttribute( 'class' );
155                         if ( c )
156                         {
157                                 var regex = new RegExp( '(?:^|\\s+)' + className + '(?=\\s|$)', 'i' );
158                                 if ( regex.test( c ) )
159                                 {
160                                         c = c.replace( regex, '' ).replace( /^\s+/, '' );
161
162                                         if ( c )
163                                                 this.setAttribute( 'class', c );
164                                         else
165                                                 this.removeAttribute( 'class' );
166                                 }
167                         }
168                 },
169
170                 hasClass : function( className )
171                 {
172                         var regex = new RegExp( '(?:^|\\s+)' + className + '(?=\\s|$)', '' );
173                         return regex.test( this.getAttribute('class') );
174                 },
175
176                 /**
177                  * Append a node as a child of this element.
178                  * @param {CKEDITOR.dom.node|String} node The node or element name to be
179                  *              appended.
180                  * @param {Boolean} [toStart] Indicates that the element is to be
181                  *              appended at the start.
182                  * @returns {CKEDITOR.dom.node} The appended node.
183                  * @example
184                  * var p = new CKEDITOR.dom.element( 'p' );
185                  *
186                  * var strong = new CKEDITOR.dom.element( 'strong' );
187                  * <b>p.append( strong );</b>
188                  *
189                  * var em = <b>p.append( 'em' );</b>
190                  *
191                  * // result: "&lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;em&gt;&lt;/em&gt;&lt;/p&gt;"
192                  */
193                 append : function( node, toStart )
194                 {
195                         if ( typeof node == 'string' )
196                                 node = this.getDocument().createElement( node );
197
198                         if ( toStart )
199                                 this.$.insertBefore( node.$, this.$.firstChild );
200                         else
201                                 this.$.appendChild( node.$ );
202
203                         return node;
204                 },
205
206                 appendHtml : function( html )
207                 {
208                         if ( !this.$.childNodes.length )
209                                 this.setHtml( html );
210                         else
211                         {
212                                 var temp = new CKEDITOR.dom.element( 'div', this.getDocument() );
213                                 temp.setHtml( html );
214                                 temp.moveChildren( this );
215                         }
216                 },
217
218                 /**
219                  * Append text to this element.
220                  * @param {String} text The text to be appended.
221                  * @returns {CKEDITOR.dom.node} The appended node.
222                  * @example
223                  * var p = new CKEDITOR.dom.element( 'p' );
224                  * p.appendText( 'This is' );
225                  * p.appendText( ' some text' );
226                  *
227                  * // result: "&lt;p&gt;This is some text&lt;/p&gt;"
228                  */
229                 appendText : function( text )
230                 {
231                         if ( this.$.text != undefined )
232                                 this.$.text += text;
233                         else
234                                 this.append( new CKEDITOR.dom.text( text ) );
235                 },
236
237                 appendBogus : function()
238                 {
239                         var lastChild = this.getLast() ;
240
241                         // Ignore empty/spaces text.
242                         while ( lastChild && lastChild.type == CKEDITOR.NODE_TEXT && !CKEDITOR.tools.rtrim( lastChild.getText() ) )
243                                 lastChild = lastChild.getPrevious();
244                         if ( !lastChild || !lastChild.is || !lastChild.is( 'br' ) )
245                         {
246                                 var bogus = CKEDITOR.env.opera ?
247                                                 this.getDocument().createText('') :
248                                                 this.getDocument().createElement( 'br' );
249
250                                 CKEDITOR.env.gecko && bogus.setAttribute( 'type', '_moz' );
251
252                                 this.append( bogus );
253                         }
254                 },
255
256                 /**
257                  * Breaks one of the ancestor element in the element position, moving
258                  * this element between the broken parts.
259                  * @param {CKEDITOR.dom.element} parent The anscestor element to get broken.
260                  * @example
261                  * // Before breaking:
262                  * //     &lt;b&gt;This &lt;i&gt;is some&lt;span /&gt; sample&lt;/i&gt; test text&lt;/b&gt;
263                  * // If "element" is &lt;span /&gt; and "parent" is &lt;i&gt;:
264                  * //     &lt;b&gt;This &lt;i&gt;is some&lt;/i&gt;&lt;span /&gt;&lt;i&gt; sample&lt;/i&gt; test text&lt;/b&gt;
265                  * element.breakParent( parent );
266                  * @example
267                  * // Before breaking:
268                  * //     &lt;b&gt;This &lt;i&gt;is some&lt;span /&gt; sample&lt;/i&gt; test text&lt;/b&gt;
269                  * // If "element" is &lt;span /&gt; and "parent" is &lt;b&gt;:
270                  * //     &lt;b&gt;This &lt;i&gt;is some&lt;/i&gt;&lt;/b&gt;&lt;span /&gt;&lt;b&gt;&lt;i&gt; sample&lt;/i&gt; test text&lt;/b&gt;
271                  * element.breakParent( parent );
272                  */
273                 breakParent : function( parent )
274                 {
275                         var range = new CKEDITOR.dom.range( this.getDocument() );
276
277                         // We'll be extracting part of this element, so let's use our
278                         // range to get the correct piece.
279                         range.setStartAfter( this );
280                         range.setEndAfter( parent );
281
282                         // Extract it.
283                         var docFrag = range.extractContents();
284
285                         // Move the element outside the broken element.
286                         range.insertNode( this.remove() );
287
288                         // Re-insert the extracted piece after the element.
289                         docFrag.insertAfterNode( this );
290                 },
291
292                 contains :
293                         CKEDITOR.env.ie || CKEDITOR.env.webkit ?
294                                 function( node )
295                                 {
296                                         var $ = this.$;
297
298                                         return node.type != CKEDITOR.NODE_ELEMENT ?
299                                                 $.contains( node.getParent().$ ) :
300                                                 $ != node.$ && $.contains( node.$ );
301                                 }
302                         :
303                                 function( node )
304                                 {
305                                         return !!( this.$.compareDocumentPosition( node.$ ) & 16 );
306                                 },
307
308                 /**
309                  * Moves the selection focus to this element.
310                  * @function
311                  * @param  {Boolean} defer Whether to asynchronously defer the
312                  *              execution by 100 ms.
313                  * @example
314                  * var element = CKEDITOR.document.getById( 'myTextarea' );
315                  * <b>element.focus()</b>;
316                  */
317                 focus : ( function()
318                 {
319                         function exec()
320                         {
321                         // IE throws error if the element is not visible.
322                         try
323                         {
324                                 this.$.focus();
325                         }
326                         catch (e)
327                         {}
328                         }
329
330                         return function( defer )
331                         {
332                                 if ( defer )
333                                         CKEDITOR.tools.setTimeout( exec, 100, this );
334                                 else
335                                         exec.call( this );
336                         };
337                 })(),
338
339                 /**
340                  * Gets the inner HTML of this element.
341                  * @returns {String} The inner HTML of this element.
342                  * @example
343                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;&lt;b&gt;Example&lt;/b&gt;&lt;/div&gt;' );
344                  * alert( <b>p.getHtml()</b> );  // "&lt;b&gt;Example&lt;/b&gt;"
345                  */
346                 getHtml : function()
347                 {
348                         var retval = this.$.innerHTML;
349                         // Strip <?xml:namespace> tags in IE. (#3341).
350                         return CKEDITOR.env.ie ? retval.replace( /<\?[^>]*>/g, '' ) : retval;
351                 },
352
353                 getOuterHtml : function()
354                 {
355                         if ( this.$.outerHTML )
356                         {
357                                 // IE includes the <?xml:namespace> tag in the outerHTML of
358                                 // namespaced element. So, we must strip it here. (#3341)
359                                 return this.$.outerHTML.replace( /<\?[^>]*>/, '' );
360                         }
361
362                         var tmpDiv = this.$.ownerDocument.createElement( 'div' );
363                         tmpDiv.appendChild( this.$.cloneNode( true ) );
364                         return tmpDiv.innerHTML;
365                 },
366
367                 /**
368                  * Sets the inner HTML of this element.
369                  * @param {String} html The HTML to be set for this element.
370                  * @returns {String} The inserted HTML.
371                  * @example
372                  * var p = new CKEDITOR.dom.element( 'p' );
373                  * <b>p.setHtml( '&lt;b&gt;Inner&lt;/b&gt; HTML' );</b>
374                  *
375                  * // result: "&lt;p&gt;&lt;b&gt;Inner&lt;/b&gt; HTML&lt;/p&gt;"
376                  */
377                 setHtml : function( html )
378                 {
379                         return ( this.$.innerHTML = html );
380                 },
381
382                 /**
383                  * Sets the element contents as plain text.
384                  * @param {String} text The text to be set.
385                  * @returns {String} The inserted text.
386                  * @example
387                  * var element = new CKEDITOR.dom.element( 'div' );
388                  * element.setText( 'A > B & C < D' );
389                  * alert( element.innerHTML );  // "A &amp;gt; B &amp;amp; C &amp;lt; D"
390                  */
391                 setText : function( text )
392                 {
393                         CKEDITOR.dom.element.prototype.setText = ( this.$.innerText != undefined ) ?
394                                 function ( text )
395                                 {
396                                         return this.$.innerText = text;
397                                 } :
398                                 function ( text )
399                                 {
400                                         return this.$.textContent = text;
401                                 };
402
403                         return this.setText( text );
404                 },
405
406                 /**
407                  * Gets the value of an element attribute.
408                  * @function
409                  * @param {String} name The attribute name.
410                  * @returns {String} The attribute value or null if not defined.
411                  * @example
412                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;input type="text" /&gt;' );
413                  * alert( <b>element.getAttribute( 'type' )</b> );  // "text"
414                  */
415                 getAttribute : (function()
416                 {
417                         var standard = function( name )
418                         {
419                                 return this.$.getAttribute( name, 2 );
420                         };
421
422                         if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) )
423                         {
424                                 return function( name )
425                                 {
426                                         switch ( name )
427                                         {
428                                                 case 'class':
429                                                         name = 'className';
430                                                         break;
431
432                                                 case 'http-equiv':
433                                                         name = 'httpEquiv';
434                                                         break;
435
436                                                 case 'name':
437                                                         return this.$.name;
438
439                                                 case 'tabindex':
440                                                         var tabIndex = standard.call( this, name );
441
442                                                         // IE returns tabIndex=0 by default for all
443                                                         // elements. For those elements,
444                                                         // getAtrribute( 'tabindex', 2 ) returns 32768
445                                                         // instead. So, we must make this check to give a
446                                                         // uniform result among all browsers.
447                                                         if ( tabIndex !== 0 && this.$.tabIndex === 0 )
448                                                                 tabIndex = null;
449
450                                                         return tabIndex;
451                                                         break;
452
453                                                 case 'checked':
454                                                 {
455                                                         var attr = this.$.attributes.getNamedItem( name ),
456                                                                 attrValue = attr.specified ? attr.nodeValue     // For value given by parser.
457                                                                                                                          : this.$.checked;  // For value created via DOM interface.
458
459                                                         return attrValue ? 'checked' : null;
460                                                 }
461
462                                                 case 'hspace':
463                                                 case 'value':
464                                                         return this.$[ name ];
465
466                                                 case 'style':
467                                                         // IE does not return inline styles via getAttribute(). See #2947.
468                                                         return this.$.style.cssText;
469                                         }
470
471                                         return standard.call( this, name );
472                                 };
473                         }
474                         else
475                                 return standard;
476                 })(),
477
478                 getChildren : function()
479                 {
480                         return new CKEDITOR.dom.nodeList( this.$.childNodes );
481                 },
482
483                 /**
484                  * Gets the current computed value of one of the element CSS style
485                  * properties.
486                  * @function
487                  * @param {String} propertyName The style property name.
488                  * @returns {String} The property value.
489                  * @example
490                  * var element = new CKEDITOR.dom.element( 'span' );
491                  * alert( <b>element.getComputedStyle( 'display' )</b> );  // "inline"
492                  */
493                 getComputedStyle :
494                         CKEDITOR.env.ie ?
495                                 function( propertyName )
496                                 {
497                                         return this.$.currentStyle[ CKEDITOR.tools.cssStyleToDomStyle( propertyName ) ];
498                                 }
499                         :
500                                 function( propertyName )
501                                 {
502                                         return this.getWindow().$.getComputedStyle( this.$, '' ).getPropertyValue( propertyName );
503                                 },
504
505                 /**
506                  * Gets the DTD entries for this element.
507                  * @returns {Object} An object containing the list of elements accepted
508                  *              by this element.
509                  */
510                 getDtd : function()
511                 {
512                         var dtd = CKEDITOR.dtd[ this.getName() ];
513
514                         this.getDtd = function()
515                         {
516                                 return dtd;
517                         };
518
519                         return dtd;
520                 },
521
522                 getElementsByTag : CKEDITOR.dom.document.prototype.getElementsByTag,
523
524                 /**
525                  * Gets the computed tabindex for this element.
526                  * @function
527                  * @returns {Number} The tabindex value.
528                  * @example
529                  * var element = CKEDITOR.document.getById( 'myDiv' );
530                  * alert( <b>element.getTabIndex()</b> );  // e.g. "-1"
531                  */
532                 getTabIndex :
533                         CKEDITOR.env.ie ?
534                                 function()
535                                 {
536                                         var tabIndex = this.$.tabIndex;
537
538                                         // IE returns tabIndex=0 by default for all elements. In
539                                         // those cases we must check that the element really has
540                                         // the tabindex attribute set to zero, or it is one of
541                                         // those element that should have zero by default.
542                                         if ( tabIndex === 0 && !CKEDITOR.dtd.$tabIndex[ this.getName() ] && parseInt( this.getAttribute( 'tabindex' ), 10 ) !== 0 )
543                                                 tabIndex = -1;
544
545                                                 return tabIndex;
546                                 }
547                         : CKEDITOR.env.webkit ?
548                                 function()
549                                 {
550                                         var tabIndex = this.$.tabIndex;
551
552                                         // Safari returns "undefined" for elements that should not
553                                         // have tabindex (like a div). So, we must try to get it
554                                         // from the attribute.
555                                         // https://bugs.webkit.org/show_bug.cgi?id=20596
556                                         if ( tabIndex == undefined )
557                                         {
558                                                 tabIndex = parseInt( this.getAttribute( 'tabindex' ), 10 );
559
560                                                 // If the element don't have the tabindex attribute,
561                                                 // then we should return -1.
562                                                 if ( isNaN( tabIndex ) )
563                                                         tabIndex = -1;
564                                         }
565
566                                         return tabIndex;
567                                 }
568                         :
569                                 function()
570                                 {
571                                         return this.$.tabIndex;
572                                 },
573
574                 /**
575                  * Gets the text value of this element.
576                  *
577                  * Only in IE (which uses innerText), &lt;br&gt; will cause linebreaks,
578                  * and sucessive whitespaces (including line breaks) will be reduced to
579                  * a single space. This behavior is ok for us, for now. It may change
580                  * in the future.
581                  * @returns {String} The text value.
582                  * @example
583                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;Same &lt;i&gt;text&lt;/i&gt;.&lt;/div&gt;' );
584                  * alert( <b>element.getText()</b> );  // "Sample text."
585                  */
586                 getText : function()
587                 {
588                         return this.$.textContent || this.$.innerText || '';
589                 },
590
591                 /**
592                  * Gets the window object that contains this element.
593                  * @returns {CKEDITOR.dom.window} The window object.
594                  * @example
595                  */
596                 getWindow : function()
597                 {
598                         return this.getDocument().getWindow();
599                 },
600
601                 /**
602                  * Gets the value of the "id" attribute of this element.
603                  * @returns {String} The element id, or null if not available.
604                  * @example
605                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;p id="myId"&gt;&lt;/p&gt;' );
606                  * alert( <b>element.getId()</b> );  // "myId"
607                  */
608                 getId : function()
609                 {
610                         return this.$.id || null;
611                 },
612
613                 /**
614                  * Gets the value of the "name" attribute of this element.
615                  * @returns {String} The element name, or null if not available.
616                  * @example
617                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;input name="myName"&gt;&lt;/input&gt;' );
618                  * alert( <b>element.getNameAtt()</b> );  // "myName"
619                  */
620                 getNameAtt : function()
621                 {
622                         return this.$.name || null;
623                 },
624
625                 /**
626                  * Gets the element name (tag name). The returned name is guaranteed to
627                  * be always full lowercased.
628                  * @returns {String} The element name.
629                  * @example
630                  * var element = new CKEDITOR.dom.element( 'span' );
631                  * alert( <b>element.getName()</b> );  // "span"
632                  */
633                 getName : function()
634                 {
635                         // Cache the lowercased name inside a closure.
636                         var nodeName = this.$.nodeName.toLowerCase();
637
638                         if ( CKEDITOR.env.ie && ! ( document.documentMode > 8 ) )
639                         {
640                                 var scopeName = this.$.scopeName;
641                                 if ( scopeName != 'HTML' )
642                                         nodeName = scopeName.toLowerCase() + ':' + nodeName;
643                         }
644
645                         return (
646                         this.getName = function()
647                                 {
648                                         return nodeName;
649                                 })();
650                 },
651
652                 /**
653                  * Gets the value set to this element. This value is usually available
654                  * for form field elements.
655                  * @returns {String} The element value.
656                  */
657                 getValue : function()
658                 {
659                         return this.$.value;
660                 },
661
662                 /**
663                  * Gets the first child node of this element.
664                  * @param {Function} evaluator Filtering the result node.
665                  * @returns {CKEDITOR.dom.node} The first child node or null if not
666                  *              available.
667                  * @example
668                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;&lt;b&gt;Example&lt;/b&gt;&lt;/div&gt;' );
669                  * var first = <b>element.getFirst()</b>;
670                  * alert( first.getName() );  // "b"
671                  */
672                 getFirst : function( evaluator )
673                 {
674                         var first = this.$.firstChild,
675                                 retval = first && new CKEDITOR.dom.node( first );
676                         if ( retval && evaluator && !evaluator( retval ) )
677                                 retval = retval.getNext( evaluator );
678
679                         return retval;
680                 },
681
682                 /**
683                  * @param {Function} evaluator Filtering the result node.
684                  */
685                 getLast : function( evaluator )
686                 {
687                         var last = this.$.lastChild,
688                                 retval = last && new CKEDITOR.dom.node( last );
689                         if ( retval && evaluator && !evaluator( retval ) )
690                                 retval = retval.getPrevious( evaluator );
691
692                         return retval;
693                 },
694
695                 getStyle : function( name )
696                 {
697                         return this.$.style[ CKEDITOR.tools.cssStyleToDomStyle( name ) ];
698                 },
699
700                 /**
701                  * Checks if the element name matches one or more names.
702                  * @param {String} name[,name[,...]] One or more names to be checked.
703                  * @returns {Boolean} true if the element name matches any of the names.
704                  * @example
705                  * var element = new CKEDITOR.element( 'span' );
706                  * alert( <b>element.is( 'span' )</b> );  "true"
707                  * alert( <b>element.is( 'p', 'span' )</b> );  "true"
708                  * alert( <b>element.is( 'p' )</b> );  "false"
709                  * alert( <b>element.is( 'p', 'div' )</b> );  "false"
710                  */
711                 is : function()
712                 {
713                         var name = this.getName();
714                         for ( var i = 0 ; i < arguments.length ; i++ )
715                         {
716                                 if ( arguments[ i ] == name )
717                                         return true;
718                         }
719                         return false;
720                 },
721
722                 /**
723                  * Decide whether one element is able to receive cursor.
724                  * @param {Boolean} [textCursor=true] Only consider element that could receive text child.
725                  */
726                 isEditable : function( textCursor )
727                 {
728                         var name = this.getName();
729
730                         if ( this.isReadOnly()
731                                         || this.getComputedStyle( 'display' ) == 'none'
732                                         || this.getComputedStyle( 'visibility' ) == 'hidden'
733                                         || CKEDITOR.dtd.$nonEditable[ name ] )
734                         {
735                                 return false;
736                         }
737
738                         if ( textCursor !== false )
739                         {
740                                 // Get the element DTD (defaults to span for unknown elements).
741                                 var dtd = CKEDITOR.dtd[ name ] || CKEDITOR.dtd.span;
742                                 // In the DTD # == text node.
743                                 return ( dtd && dtd[ '#'] );
744                         }
745
746                         return true;
747                 },
748
749                 isIdentical : function( otherElement )
750                 {
751                         if ( this.getName() != otherElement.getName() )
752                                 return false;
753
754                         var thisAttribs = this.$.attributes,
755                                 otherAttribs = otherElement.$.attributes;
756
757                         var thisLength = thisAttribs.length,
758                                 otherLength = otherAttribs.length;
759
760                         for ( var i = 0 ; i < thisLength ; i++ )
761                         {
762                                 var attribute = thisAttribs[ i ];
763
764                                 if ( attribute.nodeName == '_moz_dirty' )
765                                         continue;
766
767                                 if ( ( !CKEDITOR.env.ie || ( attribute.specified && attribute.nodeName != 'data-cke-expando' ) ) && attribute.nodeValue != otherElement.getAttribute( attribute.nodeName ) )
768                                         return false;
769                         }
770
771                         // For IE, we have to for both elements, because it's difficult to
772                         // know how the atttibutes collection is organized in its DOM.
773                         if ( CKEDITOR.env.ie )
774                         {
775                                 for ( i = 0 ; i < otherLength ; i++ )
776                                 {
777                                         attribute = otherAttribs[ i ];
778                                         if ( attribute.specified && attribute.nodeName != 'data-cke-expando'
779                                                         && attribute.nodeValue != this.getAttribute( attribute.nodeName ) )
780                                                 return false;
781                                 }
782                         }
783
784                         return true;
785                 },
786
787                 /**
788                  * Checks if this element is visible. May not work if the element is
789                  * child of an element with visibility set to "hidden", but works well
790                  * on the great majority of cases.
791                  * @return {Boolean} True if the element is visible.
792                  */
793                 isVisible : function()
794                 {
795                         var isVisible = ( this.$.offsetHeight || this.$.offsetWidth ) && this.getComputedStyle( 'visibility' ) != 'hidden',
796                                 elementWindow,
797                                 elementWindowFrame;
798
799                         // Webkit and Opera report non-zero offsetHeight despite that
800                         // element is inside an invisible iframe. (#4542)
801                         if ( isVisible && ( CKEDITOR.env.webkit || CKEDITOR.env.opera ) )
802                         {
803                                 elementWindow = this.getWindow();
804
805                                 if ( !elementWindow.equals( CKEDITOR.document.getWindow() )
806                                                 && ( elementWindowFrame = elementWindow.$.frameElement ) )
807                                 {
808                                         isVisible = new CKEDITOR.dom.element( elementWindowFrame ).isVisible();
809                                 }
810                         }
811
812                         return !!isVisible;
813                 },
814
815                 /**
816                  * Whether it's an empty inline elements which has no visual impact when removed.
817                  */
818                 isEmptyInlineRemoveable : function()
819                 {
820                         if ( !CKEDITOR.dtd.$removeEmpty[ this.getName() ] )
821                                 return false;
822
823                         var children = this.getChildren();
824                         for ( var i = 0, count = children.count(); i < count; i++ )
825                         {
826                                 var child = children.getItem( i );
827
828                                 if ( child.type == CKEDITOR.NODE_ELEMENT && child.data( 'cke-bookmark' ) )
829                                         continue;
830
831                                 if ( child.type == CKEDITOR.NODE_ELEMENT && !child.isEmptyInlineRemoveable()
832                                         || child.type == CKEDITOR.NODE_TEXT && CKEDITOR.tools.trim( child.getText() ) )
833                                 {
834                                         return false;
835                                 }
836                         }
837                         return true;
838                 },
839
840                 /**
841                  * Checks if the element has any defined attributes.
842                  * @function
843                  * @returns {Boolean} True if the element has attributes.
844                  * @example
845                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div title="Test"&gt;Example&lt;/div&gt;' );
846                  * alert( <b>element.hasAttributes()</b> );  // "true"
847                  * @example
848                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;Example&lt;/div&gt;' );
849                  * alert( <b>element.hasAttributes()</b> );  // "false"
850                  */
851                 hasAttributes :
852                         CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) ?
853                                 function()
854                                 {
855                                         var attributes = this.$.attributes;
856
857                                         for ( var i = 0 ; i < attributes.length ; i++ )
858                                         {
859                                                 var attribute = attributes[i];
860
861                                                 switch ( attribute.nodeName )
862                                                 {
863                                                         case 'class' :
864                                                                 // IE has a strange bug. If calling removeAttribute('className'),
865                                                                 // the attributes collection will still contain the "class"
866                                                                 // attribute, which will be marked as "specified", even if the
867                                                                 // outerHTML of the element is not displaying the class attribute.
868                                                                 // Note : I was not able to reproduce it outside the editor,
869                                                                 // but I've faced it while working on the TC of #1391.
870                                                                 if ( this.getAttribute( 'class' ) )
871                                                                         return true;
872
873                                                         // Attributes to be ignored.
874                                                         case 'data-cke-expando' :
875                                                                 continue;
876
877                                                         /*jsl:fallthru*/
878
879                                                         default :
880                                                                 if ( attribute.specified )
881                                                                         return true;
882                                                 }
883                                         }
884
885                                         return false;
886                                 }
887                         :
888                                 function()
889                                 {
890                                         var attrs = this.$.attributes,
891                                                 attrsNum = attrs.length;
892
893                                         // The _moz_dirty attribute might get into the element after pasting (#5455)
894                                         var execludeAttrs = { 'data-cke-expando' : 1, _moz_dirty : 1 };
895
896                                         return attrsNum > 0 &&
897                                                 ( attrsNum > 2 ||
898                                                         !execludeAttrs[ attrs[0].nodeName ] ||
899                                                         ( attrsNum == 2 && !execludeAttrs[ attrs[1].nodeName ] ) );
900                                 },
901
902                 /**
903                  * Checks if the specified attribute is defined for this element.
904                  * @returns {Boolean} True if the specified attribute is defined.
905                  * @param {String} name The attribute name.
906                  * @example
907                  */
908                 hasAttribute : (function()
909                 {
910                         function standard( name )
911                         {
912                                 var $attr = this.$.attributes.getNamedItem( name );
913                                 return !!( $attr && $attr.specified );
914                         }
915
916                         return ( CKEDITOR.env.ie && CKEDITOR.env.version < 8 ) ?
917                                         function( name )
918                                         {
919                                                 // On IE < 8 the name attribute cannot be retrieved
920                                                 // right after the element creation and setting the
921                                                 // name with setAttribute.
922                                                 if ( name == 'name' )
923                                                         return !!this.$.name;
924
925                                                 return standard.call( this, name );
926                                         }
927                                 :
928                                         standard;
929                 })(),
930
931                 /**
932                  * Hides this element (display:none).
933                  * @example
934                  * var element = CKEDITOR.dom.element.getById( 'myElement' );
935                  * <b>element.hide()</b>;
936                  */
937                 hide : function()
938                 {
939                         this.setStyle( 'display', 'none' );
940                 },
941
942                 moveChildren : function( target, toStart )
943                 {
944                         var $ = this.$;
945                         target = target.$;
946
947                         if ( $ == target )
948                                 return;
949
950                         var child;
951
952                         if ( toStart )
953                         {
954                                 while ( ( child = $.lastChild ) )
955                                         target.insertBefore( $.removeChild( child ), target.firstChild );
956                         }
957                         else
958                         {
959                                 while ( ( child = $.firstChild ) )
960                                         target.appendChild( $.removeChild( child ) );
961                         }
962                 },
963
964                 /**
965                  * Merges sibling elements that are identical to this one.<br>
966                  * <br>
967                  * Identical child elements are also merged. For example:<br>
968                  * &lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt;&lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt; =&gt; &lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt;
969                  * @function
970                  * @param {Boolean} [inlineOnly] Allow only inline elements to be merged. Defaults to "true".
971                  */
972                 mergeSiblings : ( function()
973                 {
974                         function mergeElements( element, sibling, isNext )
975                         {
976                                 if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT )
977                                 {
978                                         // Jumping over bookmark nodes and empty inline elements, e.g. <b><i></i></b>,
979                                         // queuing them to be moved later. (#5567)
980                                         var pendingNodes = [];
981
982                                         while ( sibling.data( 'cke-bookmark' )
983                                                 || sibling.isEmptyInlineRemoveable() )
984                                         {
985                                                 pendingNodes.push( sibling );
986                                                 sibling = isNext ? sibling.getNext() : sibling.getPrevious();
987                                                 if ( !sibling || sibling.type != CKEDITOR.NODE_ELEMENT )
988                                                         return;
989                                         }
990
991                                         if ( element.isIdentical( sibling ) )
992                                         {
993                                                 // Save the last child to be checked too, to merge things like
994                                                 // <b><i></i></b><b><i></i></b> => <b><i></i></b>
995                                                 var innerSibling = isNext ? element.getLast() : element.getFirst();
996
997                                                 // Move pending nodes first into the target element.
998                                                 while( pendingNodes.length )
999                                                         pendingNodes.shift().move( element, !isNext );
1000
1001                                                 sibling.moveChildren( element, !isNext );
1002                                                 sibling.remove();
1003
1004                                                 // Now check the last inner child (see two comments above).
1005                                                 if ( innerSibling && innerSibling.type == CKEDITOR.NODE_ELEMENT )
1006                                                         innerSibling.mergeSiblings();
1007                                         }
1008                                 }
1009                         }
1010
1011                         return function( inlineOnly )
1012                                 {
1013                                         if ( ! ( inlineOnly === false
1014                                                         || CKEDITOR.dtd.$removeEmpty[ this.getName() ]
1015                                                         || this.is( 'a' ) ) )   // Merge empty links and anchors also. (#5567)
1016                                         {
1017                                                 return;
1018                                         }
1019
1020                                         mergeElements( this, this.getNext(), true );
1021                                         mergeElements( this, this.getPrevious() );
1022                                 };
1023                 } )(),
1024
1025                 /**
1026                  * Shows this element (display it).
1027                  * @example
1028                  * var element = CKEDITOR.dom.element.getById( 'myElement' );
1029                  * <b>element.show()</b>;
1030                  */
1031                 show : function()
1032                 {
1033                         this.setStyles(
1034                                 {
1035                                         display : '',
1036                                         visibility : ''
1037                                 });
1038                 },
1039
1040                 /**
1041                  * Sets the value of an element attribute.
1042                  * @param {String} name The name of the attribute.
1043                  * @param {String} value The value to be set to the attribute.
1044                  * @function
1045                  * @returns {CKEDITOR.dom.element} This element instance.
1046                  * @example
1047                  * var element = CKEDITOR.dom.element.getById( 'myElement' );
1048                  * <b>element.setAttribute( 'class', 'myClass' )</b>;
1049                  * <b>element.setAttribute( 'title', 'This is an example' )</b>;
1050                  */
1051                 setAttribute : (function()
1052                 {
1053                         var standard = function( name, value )
1054                         {
1055                                 this.$.setAttribute( name, value );
1056                                 return this;
1057                         };
1058
1059                         if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) )
1060                         {
1061                                 return function( name, value )
1062                                 {
1063                                         if ( name == 'class' )
1064                                                 this.$.className = value;
1065                                         else if ( name == 'style' )
1066                                                 this.$.style.cssText = value;
1067                                         else if ( name == 'tabindex' )  // Case sensitive.
1068                                                 this.$.tabIndex = value;
1069                                         else if ( name == 'checked' )
1070                                                 this.$.checked = value;
1071                                         else
1072                                                 standard.apply( this, arguments );
1073                                         return this;
1074                                 };
1075                         }
1076                         else if ( CKEDITOR.env.ie8Compat && CKEDITOR.env.secure )
1077                         {
1078                                 return function( name, value )
1079                                 {
1080                                         // IE8 throws error when setting src attribute to non-ssl value. (#7847)
1081                                         if ( name == 'src' && value.match( /^http:\/\// ) )
1082                                                 try { standard.apply( this, arguments ); } catch( e ){}
1083                                         else
1084                                                 standard.apply( this, arguments );
1085                                         return this;
1086                                 };
1087                         }
1088                         else
1089                                 return standard;
1090                 })(),
1091
1092                 /**
1093                  * Sets the value of several element attributes.
1094                  * @param {Object} attributesPairs An object containing the names and
1095                  *              values of the attributes.
1096                  * @returns {CKEDITOR.dom.element} This element instance.
1097                  * @example
1098                  * var element = CKEDITOR.dom.element.getById( 'myElement' );
1099                  * <b>element.setAttributes({
1100                  *     'class' : 'myClass',
1101                  *     'title' : 'This is an example' })</b>;
1102                  */
1103                 setAttributes : function( attributesPairs )
1104                 {
1105                         for ( var name in attributesPairs )
1106                                 this.setAttribute( name, attributesPairs[ name ] );
1107                         return this;
1108                 },
1109
1110                 /**
1111                  * Sets the element value. This function is usually used with form
1112                  * field element.
1113                  * @param {String} value The element value.
1114                  * @returns {CKEDITOR.dom.element} This element instance.
1115                  */
1116                 setValue : function( value )
1117                 {
1118                         this.$.value = value;
1119                         return this;
1120                 },
1121
1122                 /**
1123                  * Removes an attribute from the element.
1124                  * @param {String} name The attribute name.
1125                  * @function
1126                  * @example
1127                  * var element = CKEDITOR.dom.element.createFromHtml( '<div class="classA"></div>' );
1128                  * element.removeAttribute( 'class' );
1129                  */
1130                 removeAttribute : (function()
1131                 {
1132                         var standard = function( name )
1133                         {
1134                                 this.$.removeAttribute( name );
1135                         };
1136
1137                         if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) )
1138                         {
1139                                 return function( name )
1140                                 {
1141                                         if ( name == 'class' )
1142                                                 name = 'className';
1143                                         else if ( name == 'tabindex' )
1144                                                 name = 'tabIndex';
1145                                         standard.call( this, name );
1146                                 };
1147                         }
1148                         else
1149                                 return standard;
1150                 })(),
1151
1152                 removeAttributes : function ( attributes )
1153                 {
1154                         if ( CKEDITOR.tools.isArray( attributes ) )
1155                         {
1156                                 for ( var i = 0 ; i < attributes.length ; i++ )
1157                                         this.removeAttribute( attributes[ i ] );
1158                         }
1159                         else
1160                         {
1161                                 for ( var attr in attributes )
1162                                         attributes.hasOwnProperty( attr ) && this.removeAttribute( attr );
1163                         }
1164                 },
1165
1166                 /**
1167                  * Removes a style from the element.
1168                  * @param {String} name The style name.
1169                  * @function
1170                  * @example
1171                  * var element = CKEDITOR.dom.element.createFromHtml( '<div style="display:none"></div>' );
1172                  * element.removeStyle( 'display' );
1173                  */
1174                 removeStyle : function( name )
1175                 {
1176                         this.setStyle( name, '' );
1177                         if ( this.$.style.removeAttribute )
1178                                 this.$.style.removeAttribute( CKEDITOR.tools.cssStyleToDomStyle( name ) );
1179
1180                         if ( !this.$.style.cssText )
1181                                 this.removeAttribute( 'style' );
1182                 },
1183
1184                 /**
1185                  * Sets the value of an element style.
1186                  * @param {String} name The name of the style. The CSS naming notation
1187                  *              must be used (e.g. "background-color").
1188                  * @param {String} value The value to be set to the style.
1189                  * @returns {CKEDITOR.dom.element} This element instance.
1190                  * @example
1191                  * var element = CKEDITOR.dom.element.getById( 'myElement' );
1192                  * <b>element.setStyle( 'background-color', '#ff0000' )</b>;
1193                  * <b>element.setStyle( 'margin-top', '10px' )</b>;
1194                  * <b>element.setStyle( 'float', 'right' )</b>;
1195                  */
1196                 setStyle : function( name, value )
1197                 {
1198                         this.$.style[ CKEDITOR.tools.cssStyleToDomStyle( name ) ] = value;
1199                         return this;
1200                 },
1201
1202                 /**
1203                  * Sets the value of several element styles.
1204                  * @param {Object} stylesPairs An object containing the names and
1205                  *              values of the styles.
1206                  * @returns {CKEDITOR.dom.element} This element instance.
1207                  * @example
1208                  * var element = CKEDITOR.dom.element.getById( 'myElement' );
1209                  * <b>element.setStyles({
1210                  *     'position' : 'absolute',
1211                  *     'float' : 'right' })</b>;
1212                  */
1213                 setStyles : function( stylesPairs )
1214                 {
1215                         for ( var name in stylesPairs )
1216                                 this.setStyle( name, stylesPairs[ name ] );
1217                         return this;
1218                 },
1219
1220                 /**
1221                  * Sets the opacity of an element.
1222                  * @param {Number} opacity A number within the range [0.0, 1.0].
1223                  * @example
1224                  * var element = CKEDITOR.dom.element.getById( 'myElement' );
1225                  * <b>element.setOpacity( 0.75 )</b>;
1226                  */
1227                 setOpacity : function( opacity )
1228                 {
1229                         if ( CKEDITOR.env.ie )
1230                         {
1231                                 opacity = Math.round( opacity * 100 );
1232                                 this.setStyle( 'filter', opacity >= 100 ? '' : 'progid:DXImageTransform.Microsoft.Alpha(opacity=' + opacity + ')' );
1233                         }
1234                         else
1235                                 this.setStyle( 'opacity', opacity );
1236                 },
1237
1238                 /**
1239                  * Makes the element and its children unselectable.
1240                  * @function
1241                  * @example
1242                  * var element = CKEDITOR.dom.element.getById( 'myElement' );
1243                  * element.unselectable();
1244                  */
1245                 unselectable :
1246                         CKEDITOR.env.gecko ?
1247                                 function()
1248                                 {
1249                                         this.$.style.MozUserSelect = 'none';
1250                                         this.on( 'dragstart', function( evt ) { evt.data.preventDefault(); } );
1251                                 }
1252                         : CKEDITOR.env.webkit ?
1253                                 function()
1254                                 {
1255                                         this.$.style.KhtmlUserSelect = 'none';
1256                                         this.on( 'dragstart', function( evt ) { evt.data.preventDefault(); } );
1257                                 }
1258                         :
1259                                 function()
1260                                 {
1261                                         if ( CKEDITOR.env.ie || CKEDITOR.env.opera )
1262                                         {
1263                                                 var element = this.$,
1264                                                         e,
1265                                                         i = 0;
1266
1267                                                 element.unselectable = 'on';
1268
1269                                                 while ( ( e = element.all[ i++ ] ) )
1270                                                 {
1271                                                         switch ( e.tagName.toLowerCase() )
1272                                                         {
1273                                                                 case 'iframe' :
1274                                                                 case 'textarea' :
1275                                                                 case 'input' :
1276                                                                 case 'select' :
1277                                                                         /* Ignore the above tags */
1278                                                                         break;
1279                                                                 default :
1280                                                                         e.unselectable = 'on';
1281                                                         }
1282                                                 }
1283                                         }
1284                                 },
1285
1286                 getPositionedAncestor : function()
1287                 {
1288                         var current = this;
1289                         while ( current.getName() != 'html' )
1290                         {
1291                                 if ( current.getComputedStyle( 'position' ) != 'static' )
1292                                         return current;
1293
1294                                 current = current.getParent();
1295                         }
1296                         return null;
1297                 },
1298
1299                 getDocumentPosition : function( refDocument )
1300                 {
1301                         var x = 0, y = 0,
1302                                 doc = this.getDocument(),
1303                                 body = doc.getBody(),
1304                                 quirks = doc.$.compatMode == 'BackCompat';
1305
1306                         if ( document.documentElement[ "getBoundingClientRect" ] )
1307                         {
1308                                 var box  = this.$.getBoundingClientRect(),
1309                                         $doc = doc.$,
1310                                         $docElem = $doc.documentElement;
1311
1312                                 var clientTop = $docElem.clientTop || body.$.clientTop || 0,
1313                                         clientLeft = $docElem.clientLeft || body.$.clientLeft || 0,
1314                                         needAdjustScrollAndBorders = true;
1315
1316                                 /*
1317                                  * #3804: getBoundingClientRect() works differently on IE and non-IE
1318                                  * browsers, regarding scroll positions.
1319                                  *
1320                                  * On IE, the top position of the <html> element is always 0, no matter
1321                                  * how much you scrolled down.
1322                                  *
1323                                  * On other browsers, the top position of the <html> element is negative
1324                                  * scrollTop.
1325                                  */
1326                                 if ( CKEDITOR.env.ie )
1327                                 {
1328                                         var inDocElem = doc.getDocumentElement().contains( this ),
1329                                                 inBody = doc.getBody().contains( this );
1330
1331                                         needAdjustScrollAndBorders = ( quirks && inBody ) || ( !quirks && inDocElem );
1332                                 }
1333
1334                                 if ( needAdjustScrollAndBorders )
1335                                 {
1336                                         x = box.left + ( !quirks && $docElem.scrollLeft || body.$.scrollLeft );
1337                                         x -= clientLeft;
1338                                         y = box.top  + ( !quirks && $docElem.scrollTop || body.$.scrollTop );
1339                                         y -= clientTop;
1340                                 }
1341                         }
1342                         else
1343                         {
1344                                 var current = this, previous = null, offsetParent;
1345                                 while ( current && !( current.getName() == 'body' || current.getName() == 'html' ) )
1346                                 {
1347                                         x += current.$.offsetLeft - current.$.scrollLeft;
1348                                         y += current.$.offsetTop - current.$.scrollTop;
1349
1350                                         // Opera includes clientTop|Left into offsetTop|Left.
1351                                         if ( !current.equals( this ) )
1352                                         {
1353                                                 x += ( current.$.clientLeft || 0 );
1354                                                 y += ( current.$.clientTop || 0 );
1355                                         }
1356
1357                                         var scrollElement = previous;
1358                                         while ( scrollElement && !scrollElement.equals( current ) )
1359                                         {
1360                                                 x -= scrollElement.$.scrollLeft;
1361                                                 y -= scrollElement.$.scrollTop;
1362                                                 scrollElement = scrollElement.getParent();
1363                                         }
1364
1365                                         previous = current;
1366                                         current = ( offsetParent = current.$.offsetParent ) ?
1367                                                   new CKEDITOR.dom.element( offsetParent ) : null;
1368                                 }
1369                         }
1370
1371                         if ( refDocument )
1372                         {
1373                                 var currentWindow = this.getWindow(),
1374                                         refWindow = refDocument.getWindow();
1375
1376                                 if ( !currentWindow.equals( refWindow ) && currentWindow.$.frameElement )
1377                                 {
1378                                         var iframePosition = ( new CKEDITOR.dom.element( currentWindow.$.frameElement ) ).getDocumentPosition( refDocument );
1379
1380                                         x += iframePosition.x;
1381                                         y += iframePosition.y;
1382                                 }
1383                         }
1384
1385                         if ( !document.documentElement[ "getBoundingClientRect" ] )
1386                         {
1387                                 // In Firefox, we'll endup one pixel before the element positions,
1388                                 // so we must add it here.
1389                                 if ( CKEDITOR.env.gecko && !quirks )
1390                                 {
1391                                         x += this.$.clientLeft ? 1 : 0;
1392                                         y += this.$.clientTop ? 1 : 0;
1393                                 }
1394                         }
1395
1396                         return { x : x, y : y };
1397                 },
1398
1399                 scrollIntoView : function( alignTop )
1400                 {
1401                         // Get the element window.
1402                         var win = this.getWindow(),
1403                                 winHeight = win.getViewPaneSize().height;
1404
1405                         // Starts from the offset that will be scrolled with the negative value of
1406                         // the visible window height.
1407                         var offset = winHeight * -1;
1408
1409                         // Append the view pane's height if align to top.
1410                         // Append element height if we are aligning to the bottom.
1411                         if ( alignTop )
1412                                 offset += winHeight;
1413                         else
1414                         {
1415                                 offset += this.$.offsetHeight || 0;
1416
1417                                 // Consider the margin in the scroll, which is ok for our current needs, but
1418                                 // needs investigation if we will be using this function in other places.
1419                                 offset += parseInt( this.getComputedStyle( 'marginBottom' ) || 0, 10 ) || 0;
1420                         }
1421
1422                         // Append the offsets for the entire element hierarchy.
1423                         var elementPosition = this.getDocumentPosition();
1424                         offset += elementPosition.y;
1425
1426                         // offset value might be out of range(nagative), fix it(#3692).
1427                         offset = offset < 0 ? 0 : offset;
1428
1429                         // Scroll the window to the desired position, if not already visible(#3795).
1430                         var currentScroll = win.getScrollPosition().y;
1431                         if ( offset > currentScroll || offset < currentScroll - winHeight )
1432                                 win.$.scrollTo( 0, offset );
1433                 },
1434
1435                 setState : function( state )
1436                 {
1437                         switch ( state )
1438                         {
1439                                 case CKEDITOR.TRISTATE_ON :
1440                                         this.addClass( 'cke_on' );
1441                                         this.removeClass( 'cke_off' );
1442                                         this.removeClass( 'cke_disabled' );
1443                                         break;
1444                                 case CKEDITOR.TRISTATE_DISABLED :
1445                                         this.addClass( 'cke_disabled' );
1446                                         this.removeClass( 'cke_off' );
1447                                         this.removeClass( 'cke_on' );
1448                                         break;
1449                                 default :
1450                                         this.addClass( 'cke_off' );
1451                                         this.removeClass( 'cke_on' );
1452                                         this.removeClass( 'cke_disabled' );
1453                                         break;
1454                         }
1455                 },
1456
1457                 /**
1458                  * Returns the inner document of this IFRAME element.
1459                  * @returns {CKEDITOR.dom.document} The inner document.
1460                  */
1461                 getFrameDocument : function()
1462                 {
1463                         var $ = this.$;
1464
1465                         try
1466                         {
1467                                 // In IE, with custom document.domain, it may happen that
1468                                 // the iframe is not yet available, resulting in "Access
1469                                 // Denied" for the following property access.
1470                                 $.contentWindow.document;
1471                         }
1472                         catch ( e )
1473                         {
1474                                 // Trick to solve this issue, forcing the iframe to get ready
1475                                 // by simply setting its "src" property.
1476                                 $.src = $.src;
1477
1478                                 // In IE6 though, the above is not enough, so we must pause the
1479                                 // execution for a while, giving it time to think.
1480                                 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 7 )
1481                                 {
1482                                         window.showModalDialog(
1483                                                 'javascript:document.write("' +
1484                                                         '<script>' +
1485                                                                 'window.setTimeout(' +
1486                                                                         'function(){window.close();}' +
1487                                                                         ',50);' +
1488                                                         '</script>")' );
1489                                 }
1490                         }
1491
1492                         return $ && new CKEDITOR.dom.document( $.contentWindow.document );
1493                 },
1494
1495                 /**
1496                  * Copy all the attributes from one node to the other, kinda like a clone
1497                  * skipAttributes is an object with the attributes that must NOT be copied.
1498                  * @param {CKEDITOR.dom.element} dest The destination element.
1499                  * @param {Object} skipAttributes A dictionary of attributes to skip.
1500                  * @example
1501                  */
1502                 copyAttributes : function( dest, skipAttributes )
1503                 {
1504                         var attributes = this.$.attributes;
1505                         skipAttributes = skipAttributes || {};
1506
1507                         for ( var n = 0 ; n < attributes.length ; n++ )
1508                         {
1509                                 var attribute = attributes[n];
1510
1511                                 // Lowercase attribute name hard rule is broken for
1512                                 // some attribute on IE, e.g. CHECKED.
1513                                 var attrName = attribute.nodeName.toLowerCase(),
1514                                         attrValue;
1515
1516                                 // We can set the type only once, so do it with the proper value, not copying it.
1517                                 if ( attrName in skipAttributes )
1518                                         continue;
1519
1520                                 if ( attrName == 'checked' && ( attrValue = this.getAttribute( attrName ) ) )
1521                                         dest.setAttribute( attrName, attrValue );
1522                                 // IE BUG: value attribute is never specified even if it exists.
1523                                 else if ( attribute.specified ||
1524                                   ( CKEDITOR.env.ie && attribute.nodeValue && attrName == 'value' ) )
1525                                 {
1526                                         attrValue = this.getAttribute( attrName );
1527                                         if ( attrValue === null )
1528                                                 attrValue = attribute.nodeValue;
1529
1530                                         dest.setAttribute( attrName, attrValue );
1531                                 }
1532                         }
1533
1534                         // The style:
1535                         if ( this.$.style.cssText !== '' )
1536                                 dest.$.style.cssText = this.$.style.cssText;
1537                 },
1538
1539                 /**
1540                  * Changes the tag name of the current element.
1541                  * @param {String} newTag The new tag for the element.
1542                  */
1543                 renameNode : function( newTag )
1544                 {
1545                         // If it's already correct exit here.
1546                         if ( this.getName() == newTag )
1547                                 return;
1548
1549                         var doc = this.getDocument();
1550
1551                         // Create the new node.
1552                         var newNode = new CKEDITOR.dom.element( newTag, doc );
1553
1554                         // Copy all attributes.
1555                         this.copyAttributes( newNode );
1556
1557                         // Move children to the new node.
1558                         this.moveChildren( newNode );
1559
1560                         // Replace the node.
1561                         this.getParent() && this.$.parentNode.replaceChild( newNode.$, this.$ );
1562                         newNode.$[ 'data-cke-expando' ] = this.$[ 'data-cke-expando' ];
1563                         this.$ = newNode.$;
1564                 },
1565
1566                 /**
1567                  * Gets a DOM tree descendant under the current node.
1568                  * @param {Array|Number} indices The child index or array of child indices under the node.
1569                  * @returns {CKEDITOR.dom.node} The specified DOM child under the current node. Null if child does not exist.
1570                  * @example
1571                  * var strong = p.getChild(0);
1572                  */
1573                 getChild : function( indices )
1574                 {
1575                         var rawNode = this.$;
1576
1577                         if ( !indices.slice )
1578                                 rawNode = rawNode.childNodes[ indices ];
1579                         else
1580                         {
1581                                 while ( indices.length > 0 && rawNode )
1582                                         rawNode = rawNode.childNodes[ indices.shift() ];
1583                         }
1584
1585                         return rawNode ? new CKEDITOR.dom.node( rawNode ) : null;
1586                 },
1587
1588                 getChildCount : function()
1589                 {
1590                         return this.$.childNodes.length;
1591                 },
1592
1593                 disableContextMenu : function()
1594                 {
1595                         this.on( 'contextmenu', function( event )
1596                                 {
1597                                         // Cancel the browser context menu.
1598                                         if ( !event.data.getTarget().hasClass( 'cke_enable_context_menu' ) )
1599                                                 event.data.preventDefault();
1600                                 } );
1601                 },
1602
1603                 /**
1604                  * Gets element's direction. Supports both CSS 'direction' prop and 'dir' attr.
1605                  */
1606                 getDirection : function( useComputed )
1607                 {
1608                         return useComputed ?
1609                                 this.getComputedStyle( 'direction' )
1610                                         // Webkit: offline element returns empty direction (#8053).
1611                                         || this.getDirection()
1612                                         || this.getDocument().$.dir
1613                                         || this.getDocument().getBody().getDirection( 1 )
1614                                 : this.getStyle( 'direction' ) || this.getAttribute( 'dir' );
1615                 },
1616
1617                 /**
1618                  * Gets, sets and removes custom data to be stored as HTML5 data-* attributes.
1619                  * @param {String} name The name of the attribute, excluding the 'data-' part.
1620                  * @param {String} [value] The value to set. If set to false, the attribute will be removed.
1621                  * @example
1622                  * element.data( 'extra-info', 'test' );   // appended the attribute data-extra-info="test" to the element
1623                  * alert( element.data( 'extra-info' ) );  // "test"
1624                  * element.data( 'extra-info', false );    // remove the data-extra-info attribute from the element
1625                  */
1626                 data : function ( name, value )
1627                 {
1628                         name = 'data-' + name;
1629                         if ( value === undefined )
1630                                 return this.getAttribute( name );
1631                         else if ( value === false )
1632                                 this.removeAttribute( name );
1633                         else
1634                                 this.setAttribute( name, value );
1635
1636                         return null;
1637                 }
1638         });
1639
1640 ( function()
1641 {
1642         var sides = {
1643                 width : [ "border-left-width", "border-right-width","padding-left", "padding-right" ],
1644                 height : [ "border-top-width", "border-bottom-width", "padding-top",  "padding-bottom" ]
1645         };
1646
1647         function marginAndPaddingSize( type )
1648         {
1649                 var adjustment = 0;
1650                 for ( var i = 0, len = sides[ type ].length; i < len; i++ )
1651                         adjustment += parseInt( this.getComputedStyle( sides [ type ][ i ] ) || 0, 10 ) || 0;
1652                 return adjustment;
1653         }
1654
1655         /**
1656          * Sets the element size considering the box model.
1657          * @name CKEDITOR.dom.element.prototype.setSize
1658          * @function
1659          * @param {String} type The dimension to set. It accepts "width" and "height".
1660          * @param {Number} size The length unit in px.
1661          * @param {Boolean} isBorderBox Apply the size based on the border box model.
1662          */
1663         CKEDITOR.dom.element.prototype.setSize = function( type, size, isBorderBox )
1664                 {
1665                         if ( typeof size == 'number' )
1666                         {
1667                                 if ( isBorderBox && !( CKEDITOR.env.ie && CKEDITOR.env.quirks ) )
1668                                         size -= marginAndPaddingSize.call( this, type );
1669
1670                                 this.setStyle( type, size + 'px' );
1671                         }
1672                 };
1673
1674         /**
1675          * Gets the element size, possibly considering the box model.
1676          * @name CKEDITOR.dom.element.prototype.getSize
1677          * @function
1678          * @param {String} type The dimension to get. It accepts "width" and "height".
1679          * @param {Boolean} isBorderBox Get the size based on the border box model.
1680          */
1681         CKEDITOR.dom.element.prototype.getSize = function( type, isBorderBox )
1682                 {
1683                         var size = Math.max( this.$[ 'offset' + CKEDITOR.tools.capitalize( type )  ],
1684                                 this.$[ 'client' + CKEDITOR.tools.capitalize( type )  ] ) || 0;
1685
1686                         if ( isBorderBox )
1687                                 size -= marginAndPaddingSize.call( this, type );
1688
1689                         return size;
1690                 };
1691 })();