JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.6.6.1
[ckeditor.git] / _source / core / dom / element.js
index c4e8876..bb315a4 100644 (file)
@@ -1,5 +1,5 @@
 /*\r
-Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.\r
+Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.\r
 For licensing, see LICENSE.html or http://ckeditor.com/license\r
 */\r
 \r
@@ -88,7 +88,7 @@ CKEDITOR.dom.element.setMarker = function( database, element, name, value )
 CKEDITOR.dom.element.clearAllMarkers = function( database )\r
 {\r
        for ( var i in database )\r
-               CKEDITOR.dom.element.clearMarkers( database, database[i], true );\r
+               CKEDITOR.dom.element.clearMarkers( database, database[i], 1 );\r
 };\r
 \r
 CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatabase )\r
@@ -105,6 +105,9 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab
        }\r
 };\r
 \r
+( function()\r
+{\r
+\r
 CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,\r
        /** @lends CKEDITOR.dom.element.prototype */\r
        {\r
@@ -243,10 +246,13 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                                lastChild = lastChild.getPrevious();\r
                        if ( !lastChild || !lastChild.is || !lastChild.is( 'br' ) )\r
                        {\r
-                               this.append(\r
-                                       CKEDITOR.env.opera ?\r
+                               var bogus = CKEDITOR.env.opera ?\r
                                                this.getDocument().createText('') :\r
-                                               this.getDocument().createElement( 'br' ) );\r
+                                               this.getDocument().createElement( 'br' );\r
+\r
+                               CKEDITOR.env.gecko && bogus.setAttribute( 'type', '_moz' );\r
+\r
+                               this.append( bogus );\r
                        }\r
                },\r
 \r
@@ -256,15 +262,15 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                 * @param {CKEDITOR.dom.element} parent The anscestor element to get broken.\r
                 * @example\r
                 * // Before breaking:\r
-                * //     <b>This <i>is some<span /> sample</i> test text</b>\r
-                * // If "element" is <span /> and "parent" is <i>:\r
-                * //     <b>This <i>is some</i><span /><i> sample</i> test text</b>\r
+                * //     &lt;b&gt;This &lt;i&gt;is some&lt;span /&gt; sample&lt;/i&gt; test text&lt;/b&gt;\r
+                * // If "element" is &lt;span /&gt; and "parent" is &lt;i&gt;:\r
+                * //     &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;\r
                 * element.breakParent( parent );\r
                 * @example\r
                 * // Before breaking:\r
-                * //     <b>This <i>is some<span /> sample</i> test text</b>\r
-                * // If "element" is <span /> and "parent" is <b>:\r
-                * //     <b>This <i>is some</i></b><span /><b><i> sample</i> test text</b>\r
+                * //     &lt;b&gt;This &lt;i&gt;is some&lt;span /&gt; sample&lt;/i&gt; test text&lt;/b&gt;\r
+                * // If "element" is &lt;span /&gt; and "parent" is &lt;b&gt;:\r
+                * //     &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;\r
                 * element.breakParent( parent );\r
                 */\r
                breakParent : function( parent )\r
@@ -304,12 +310,17 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
 \r
                /**\r
                 * Moves the selection focus to this element.\r
+                * @function\r
+                * @param  {Boolean} defer Whether to asynchronously defer the\r
+                *              execution by 100 ms.\r
                 * @example\r
                 * var element = CKEDITOR.document.getById( 'myTextarea' );\r
                 * <b>element.focus()</b>;\r
                 */\r
-               focus : function()\r
+               focus : ( function()\r
                {\r
+                       function exec()\r
+                       {\r
                        // IE throws error if the element is not visible.\r
                        try\r
                        {\r
@@ -317,7 +328,16 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                        }\r
                        catch (e)\r
                        {}\r
-               },\r
+                       }\r
+\r
+                       return function( defer )\r
+                       {\r
+                               if ( defer )\r
+                                       CKEDITOR.tools.setTimeout( exec, 100, this );\r
+                               else\r
+                                       exec.call( this );\r
+                       };\r
+               })(),\r
 \r
                /**\r
                 * Gets the inner HTML of this element.\r
@@ -328,7 +348,9 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                 */\r
                getHtml : function()\r
                {\r
-                       return this.$.innerHTML;\r
+                       var retval = this.$.innerHTML;\r
+                       // Strip <?xml:namespace> tags in IE. (#3341).\r
+                       return CKEDITOR.env.ie ? retval.replace( /<\?[^>]*>/g, '' ) : retval;\r
                },\r
 \r
                getOuterHtml : function()\r
@@ -410,6 +432,13 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                                                        name = 'className';\r
                                                        break;\r
 \r
+                                               case 'http-equiv':\r
+                                                       name = 'httpEquiv';\r
+                                                       break;\r
+\r
+                                               case 'name':\r
+                                                       return this.$.name;\r
+\r
                                                case 'tabindex':\r
                                                        var tabIndex = standard.call( this, name );\r
 \r
@@ -425,12 +454,26 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                                                        break;\r
 \r
                                                case 'checked':\r
-                                                       return this.$.checked;\r
-                                                       break;\r
+                                               {\r
+                                                       var attr = this.$.attributes.getNamedItem( name ),\r
+                                                               attrValue = attr.specified ? attr.nodeValue     // For value given by parser.\r
+                                                                                                                        : this.$.checked;  // For value created via DOM interface.\r
+\r
+                                                       return attrValue ? 'checked' : null;\r
+                                               }\r
+\r
+                                               case 'hspace':\r
+                                               case 'value':\r
+                                                       return this.$[ name ];\r
 \r
                                                case 'style':\r
                                                        // IE does not return inline styles via getAttribute(). See #2947.\r
                                                        return this.$.style.cssText;\r
+\r
+                                               case 'contenteditable':\r
+                                               case 'contentEditable':\r
+                                                       return this.$.attributes.getNamedItem( 'contentEditable' ).specified ?\r
+                                                                       this.$.getAttribute( 'contentEditable' ) : null;\r
                                        }\r
 \r
                                        return standard.call( this, name );\r
@@ -464,7 +507,10 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                        :\r
                                function( propertyName )\r
                                {\r
-                                       return this.getWindow().$.getComputedStyle( this.$, '' ).getPropertyValue( propertyName );\r
+                                       var style = this.getWindow().$.getComputedStyle( this.$, null );\r
+\r
+                                       // Firefox may return null if we call the above on a hidden iframe. (#9117)\r
+                                       return style ? style.getPropertyValue( propertyName ) : '';\r
                                },\r
 \r
                /**\r
@@ -545,7 +591,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                 * in the future.\r
                 * @returns {String} The text value.\r
                 * @example\r
-                * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;Same &lt;i&gt;text&lt;/i&gt;.&lt;/div&gt;' );\r
+                * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;Sample &lt;i&gt;text&lt;/i&gt;.&lt;/div&gt;' );\r
                 * alert( <b>element.getText()</b> );  // "Sample text."\r
                 */\r
                getText : function()\r
@@ -600,7 +646,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                        // Cache the lowercased name inside a closure.\r
                        var nodeName = this.$.nodeName.toLowerCase();\r
 \r
-                       if ( CKEDITOR.env.ie )\r
+                       if ( CKEDITOR.env.ie && ! ( document.documentMode > 8 ) )\r
                        {\r
                                var scopeName = this.$.scopeName;\r
                                if ( scopeName != 'HTML' )\r
@@ -608,7 +654,6 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                        }\r
 \r
                        return (\r
-                       /** @ignore */\r
                        this.getName = function()\r
                                {\r
                                        return nodeName;\r
@@ -627,6 +672,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
 \r
                /**\r
                 * Gets the first child node of this element.\r
+                * @param {Function} evaluator Filtering the result node.\r
                 * @returns {CKEDITOR.dom.node} The first child node or null if not\r
                 *              available.\r
                 * @example\r
@@ -634,10 +680,14 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                 * var first = <b>element.getFirst()</b>;\r
                 * alert( first.getName() );  // "b"\r
                 */\r
-               getFirst : function()\r
+               getFirst : function( evaluator )\r
                {\r
-                       var $ = this.$.firstChild;\r
-                       return $ ? new CKEDITOR.dom.node( $ ) : null;\r
+                       var first = this.$.firstChild,\r
+                               retval = first && new CKEDITOR.dom.node( first );\r
+                       if ( retval && evaluator && !evaluator( retval ) )\r
+                               retval = retval.getNext( evaluator );\r
+\r
+                       return retval;\r
                },\r
 \r
                /**\r
@@ -680,17 +730,33 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                        return false;\r
                },\r
 \r
-               isEditable : function()\r
+               /**\r
+                * Decide whether one element is able to receive cursor.\r
+                * @param {Boolean} [textCursor=true] Only consider element that could receive text child.\r
+                */\r
+               isEditable : function( textCursor )\r
                {\r
-                       // Get the element name.\r
                        var name = this.getName();\r
 \r
-                       // Get the element DTD (defaults to span for unknown elements).\r
-                       var dtd = !CKEDITOR.dtd.$nonEditable[ name ]\r
-                                               && ( CKEDITOR.dtd[ name ] || CKEDITOR.dtd.span );\r
+                       if ( this.isReadOnly()\r
+                                       || this.getComputedStyle( 'display' ) == 'none'\r
+                                       || this.getComputedStyle( 'visibility' ) == 'hidden'\r
+                                       || this.is( 'a' ) && this.data( 'cke-saved-name' ) && !this.getChildCount()\r
+                                       || CKEDITOR.dtd.$nonEditable[ name ]\r
+                                       || CKEDITOR.dtd.$empty[ name ] )\r
+                       {\r
+                               return false;\r
+                       }\r
 \r
-                       // In the DTD # == text node.\r
-                       return ( dtd && dtd['#'] );\r
+                       if ( textCursor !== false )\r
+                       {\r
+                               // Get the element DTD (defaults to span for unknown elements).\r
+                               var dtd = CKEDITOR.dtd[ name ] || CKEDITOR.dtd.span;\r
+                               // In the DTD # == text node.\r
+                               return ( dtd && dtd[ '#'] );\r
+                       }\r
+\r
+                       return true;\r
                },\r
 \r
                isIdentical : function( otherElement )\r
@@ -704,14 +770,14 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                        var thisLength = thisAttribs.length,\r
                                otherLength = otherAttribs.length;\r
 \r
-                       if ( !CKEDITOR.env.ie && thisLength != otherLength )\r
-                               return false;\r
-\r
                        for ( var i = 0 ; i < thisLength ; i++ )\r
                        {\r
                                var attribute = thisAttribs[ i ];\r
 \r
-                               if ( ( !CKEDITOR.env.ie || ( attribute.specified && attribute.nodeName != '_cke_expando' ) ) && attribute.nodeValue != otherElement.getAttribute( attribute.nodeName ) )\r
+                               if ( attribute.nodeName == '_moz_dirty' )\r
+                                       continue;\r
+\r
+                               if ( ( !CKEDITOR.env.ie || ( attribute.specified && attribute.nodeName != 'data-cke-expando' ) ) && attribute.nodeValue != otherElement.getAttribute( attribute.nodeName ) )\r
                                        return false;\r
                        }\r
 \r
@@ -722,8 +788,8 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                                for ( i = 0 ; i < otherLength ; i++ )\r
                                {\r
                                        attribute = otherAttribs[ i ];\r
-\r
-                                       if ( ( !CKEDITOR.env.ie || ( attribute.specified && attribute.nodeName != '_cke_expando' ) ) && attribute.nodeValue != thisAttribs.getAttribute( attribute.nodeName ) )\r
+                                       if ( attribute.specified && attribute.nodeName != 'data-cke-expando'\r
+                                                       && attribute.nodeValue != this.getAttribute( attribute.nodeName ) )\r
                                                return false;\r
                                }\r
                        }\r
@@ -739,18 +805,61 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                 */\r
                isVisible : function()\r
                {\r
-                       return this.$.offsetWidth && ( this.$.style.visibility != 'hidden' );\r
+                       var isVisible = ( this.$.offsetHeight || this.$.offsetWidth ) && this.getComputedStyle( 'visibility' ) != 'hidden',\r
+                               elementWindow,\r
+                               elementWindowFrame;\r
+\r
+                       // Webkit and Opera report non-zero offsetHeight despite that\r
+                       // element is inside an invisible iframe. (#4542)\r
+                       if ( isVisible && ( CKEDITOR.env.webkit || CKEDITOR.env.opera ) )\r
+                       {\r
+                               elementWindow = this.getWindow();\r
+\r
+                               if ( !elementWindow.equals( CKEDITOR.document.getWindow() )\r
+                                               && ( elementWindowFrame = elementWindow.$.frameElement ) )\r
+                               {\r
+                                       isVisible = new CKEDITOR.dom.element( elementWindowFrame ).isVisible();\r
+                               }\r
+                       }\r
+\r
+                       return !!isVisible;\r
                },\r
 \r
                /**\r
-                * Indicates that the element has defined attributes.\r
+                * Whether it's an empty inline elements which has no visual impact when removed.\r
+                */\r
+               isEmptyInlineRemoveable : function()\r
+               {\r
+                       if ( !CKEDITOR.dtd.$removeEmpty[ this.getName() ] )\r
+                               return false;\r
+\r
+                       var children = this.getChildren();\r
+                       for ( var i = 0, count = children.count(); i < count; i++ )\r
+                       {\r
+                               var child = children.getItem( i );\r
+\r
+                               if ( child.type == CKEDITOR.NODE_ELEMENT && child.data( 'cke-bookmark' ) )\r
+                                       continue;\r
+\r
+                               if ( child.type == CKEDITOR.NODE_ELEMENT && !child.isEmptyInlineRemoveable()\r
+                                       || child.type == CKEDITOR.NODE_TEXT && CKEDITOR.tools.trim( child.getText() ) )\r
+                               {\r
+                                       return false;\r
+                               }\r
+                       }\r
+                       return true;\r
+               },\r
+\r
+               /**\r
+                * Checks if the element has any defined attributes.\r
+                * @function\r
                 * @returns {Boolean} True if the element has attributes.\r
                 * @example\r
-                * var element = CKEDITOR.dom.element.createFromHtml( '<div title="Test">Example</div>' );\r
-                * alert( <b>element.hasAttributes()</b> );  "true"\r
+                * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div title="Test"&gt;Example&lt;/div&gt;' );\r
+                * alert( <b>element.hasAttributes()</b> );  // "true"\r
                 * @example\r
-                * var element = CKEDITOR.dom.element.createFromHtml( '<div>Example</div>' );\r
-                * alert( <b>element.hasAttributes()</b> );  "false"\r
+                * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;Example&lt;/div&gt;' );\r
+                * alert( <b>element.hasAttributes()</b> );  // "false"\r
                 */\r
                hasAttributes :\r
                        CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) ?\r
@@ -775,7 +884,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                                                                        return true;\r
 \r
                                                        // Attributes to be ignored.\r
-                                                       case '_cke_expando' :\r
+                                                       case 'data-cke-expando' :\r
                                                                continue;\r
 \r
                                                        /*jsl:fallthru*/\r
@@ -791,21 +900,46 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                        :\r
                                function()\r
                                {\r
-                                       var attributes = this.$.attributes;\r
-                                       return ( attributes.length > 1 || ( attributes.length == 1 && attributes[0].nodeName != '_cke_expando' ) );\r
+                                       var attrs = this.$.attributes,\r
+                                               attrsNum = attrs.length;\r
+\r
+                                       // The _moz_dirty attribute might get into the element after pasting (#5455)\r
+                                       var execludeAttrs = { 'data-cke-expando' : 1, _moz_dirty : 1 };\r
+\r
+                                       return attrsNum > 0 &&\r
+                                               ( attrsNum > 2 ||\r
+                                                       !execludeAttrs[ attrs[0].nodeName ] ||\r
+                                                       ( attrsNum == 2 && !execludeAttrs[ attrs[1].nodeName ] ) );\r
                                },\r
 \r
                /**\r
-                * Indicates whether a specified attribute is defined for this element.\r
+                * Checks if the specified attribute is defined for this element.\r
                 * @returns {Boolean} True if the specified attribute is defined.\r
-                * @param (String) name The attribute name.\r
+                * @param {String} name The attribute name.\r
                 * @example\r
                 */\r
-               hasAttribute : function( name )\r
+               hasAttribute : (function()\r
                {\r
-                       var $attr = this.$.attributes.getNamedItem( name );\r
-                       return !!( $attr && $attr.specified );\r
-               },\r
+                       function standard( name )\r
+                       {\r
+                               var $attr = this.$.attributes.getNamedItem( name );\r
+                               return !!( $attr && $attr.specified );\r
+                       }\r
+\r
+                       return ( CKEDITOR.env.ie && CKEDITOR.env.version < 8 ) ?\r
+                                       function( name )\r
+                                       {\r
+                                               // On IE < 8 the name attribute cannot be retrieved\r
+                                               // right after the element creation and setting the\r
+                                               // name with setAttribute.\r
+                                               if ( name == 'name' )\r
+                                                       return !!this.$.name;\r
+\r
+                                               return standard.call( this, name );\r
+                                       }\r
+                               :\r
+                                       standard;\r
+               })(),\r
 \r
                /**\r
                 * Hides this element (display:none).\r
@@ -841,6 +975,67 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                },\r
 \r
                /**\r
+                * Merges sibling elements that are identical to this one.<br>\r
+                * <br>\r
+                * Identical child elements are also merged. For example:<br>\r
+                * &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;\r
+                * @function\r
+                * @param {Boolean} [inlineOnly] Allow only inline elements to be merged. Defaults to "true".\r
+                */\r
+               mergeSiblings : ( function()\r
+               {\r
+                       function mergeElements( element, sibling, isNext )\r
+                       {\r
+                               if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT )\r
+                               {\r
+                                       // Jumping over bookmark nodes and empty inline elements, e.g. <b><i></i></b>,\r
+                                       // queuing them to be moved later. (#5567)\r
+                                       var pendingNodes = [];\r
+\r
+                                       while ( sibling.data( 'cke-bookmark' )\r
+                                               || sibling.isEmptyInlineRemoveable() )\r
+                                       {\r
+                                               pendingNodes.push( sibling );\r
+                                               sibling = isNext ? sibling.getNext() : sibling.getPrevious();\r
+                                               if ( !sibling || sibling.type != CKEDITOR.NODE_ELEMENT )\r
+                                                       return;\r
+                                       }\r
+\r
+                                       if ( element.isIdentical( sibling ) )\r
+                                       {\r
+                                               // Save the last child to be checked too, to merge things like\r
+                                               // <b><i></i></b><b><i></i></b> => <b><i></i></b>\r
+                                               var innerSibling = isNext ? element.getLast() : element.getFirst();\r
+\r
+                                               // Move pending nodes first into the target element.\r
+                                               while( pendingNodes.length )\r
+                                                       pendingNodes.shift().move( element, !isNext );\r
+\r
+                                               sibling.moveChildren( element, !isNext );\r
+                                               sibling.remove();\r
+\r
+                                               // Now check the last inner child (see two comments above).\r
+                                               if ( innerSibling && innerSibling.type == CKEDITOR.NODE_ELEMENT )\r
+                                                       innerSibling.mergeSiblings();\r
+                                       }\r
+                               }\r
+                       }\r
+\r
+                       return function( inlineOnly )\r
+                               {\r
+                                       if ( ! ( inlineOnly === false\r
+                                                       || CKEDITOR.dtd.$removeEmpty[ this.getName() ]\r
+                                                       || this.is( 'a' ) ) )   // Merge empty links and anchors also. (#5567)\r
+                                       {\r
+                                               return;\r
+                                       }\r
+\r
+                                       mergeElements( this, this.getNext(), true );\r
+                                       mergeElements( this, this.getPrevious() );\r
+                               };\r
+               } )(),\r
+\r
+               /**\r
                 * Shows this element (display it).\r
                 * @example\r
                 * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
@@ -886,6 +1081,20 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                                                this.$.tabIndex = value;\r
                                        else if ( name == 'checked' )\r
                                                this.$.checked = value;\r
+                                       else if ( name == 'contenteditable' )\r
+                                               standard.call( this, 'contentEditable', value );\r
+                                       else\r
+                                               standard.apply( this, arguments );\r
+                                       return this;\r
+                               };\r
+                       }\r
+                       else if ( CKEDITOR.env.ie8Compat && CKEDITOR.env.secure )\r
+                       {\r
+                               return function( name, value )\r
+                               {\r
+                                       // IE8 throws error when setting src attribute to non-ssl value. (#7847)\r
+                                       if ( name == 'src' && value.match( /^http:\/\// ) )\r
+                                               try { standard.apply( this, arguments ); } catch( e ){}\r
                                        else\r
                                                standard.apply( this, arguments );\r
                                        return this;\r
@@ -948,6 +1157,8 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                                                name = 'className';\r
                                        else if ( name == 'tabindex' )\r
                                                name = 'tabIndex';\r
+                                       else if ( name == 'contenteditable' )\r
+                                               name = 'contentEditable';\r
                                        standard.call( this, name );\r
                                };\r
                        }\r
@@ -957,8 +1168,16 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
 \r
                removeAttributes : function ( attributes )\r
                {\r
-                       for ( var i = 0 ; i < attributes.length ; i++ )\r
-                               this.removeAttribute( attributes[ i ] );\r
+                       if ( CKEDITOR.tools.isArray( attributes ) )\r
+                       {\r
+                               for ( var i = 0 ; i < attributes.length ; i++ )\r
+                                       this.removeAttribute( attributes[ i ] );\r
+                       }\r
+                       else\r
+                       {\r
+                               for ( var attr in attributes )\r
+                                       attributes.hasOwnProperty( attr ) && this.removeAttribute( attr );\r
+                       }\r
                },\r
 \r
                /**\r
@@ -971,10 +1190,19 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                 */\r
                removeStyle : function( name )\r
                {\r
-                       if ( this.$.style.removeAttribute )\r
-                               this.$.style.removeAttribute( CKEDITOR.tools.cssStyleToDomStyle( name ) );\r
-                       else\r
-                               this.setStyle( name, '' );\r
+                       // Removes the specified property from the current style object.\r
+                       var $ = this.$.style;\r
+\r
+                       // "removeProperty" need to be specific on the following styles.\r
+                       if ( !$.removeProperty && ( name == 'border' || name == 'margin' || name == 'padding' ) )\r
+                       {\r
+                               var names = expandedRules( name );\r
+                               for ( var i = 0 ; i < names.length ; i++ )\r
+                                       this.removeStyle( names[ i ] );\r
+                               return;\r
+                       }\r
+\r
+                       $.removeProperty ? $.removeProperty( name ) : $.removeAttribute( CKEDITOR.tools.cssStyleToDomStyle( name ) );\r
 \r
                        if ( !this.$.style.cssText )\r
                                this.removeAttribute( 'style' );\r
@@ -1025,7 +1253,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                 */\r
                setOpacity : function( opacity )\r
                {\r
-                       if ( CKEDITOR.env.ie )\r
+                       if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 )\r
                        {\r
                                opacity = Math.round( opacity * 100 );\r
                                this.setStyle( 'filter', opacity >= 100 ? '' : 'progid:DXImageTransform.Microsoft.Alpha(opacity=' + opacity + ')' );\r
@@ -1046,11 +1274,13 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                                function()\r
                                {\r
                                        this.$.style.MozUserSelect = 'none';\r
+                                       this.on( 'dragstart', function( evt ) { evt.data.preventDefault(); } );\r
                                }\r
                        : CKEDITOR.env.webkit ?\r
                                function()\r
                                {\r
                                        this.$.style.KhtmlUserSelect = 'none';\r
+                                       this.on( 'dragstart', function( evt ) { evt.data.preventDefault(); } );\r
                                }\r
                        :\r
                                function()\r
@@ -1058,12 +1288,13 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                                        if ( CKEDITOR.env.ie || CKEDITOR.env.opera )\r
                                        {\r
                                                var element = this.$,\r
+                                                       elements = element.getElementsByTagName("*"),\r
                                                        e,\r
                                                        i = 0;\r
 \r
                                                element.unselectable = 'on';\r
 \r
-                                               while ( ( e = element.all[ i++ ] ) )\r
+                                               while ( ( e = elements[ i++ ] ) )\r
                                                {\r
                                                        switch ( e.tagName.toLowerCase() )\r
                                                        {\r
@@ -1096,10 +1327,9 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                getDocumentPosition : function( refDocument )\r
                {\r
                        var x = 0, y = 0,\r
-                               body = this.getDocument().getBody(),\r
-                               quirks = this.getDocument().$.compatMode == 'BackCompat';\r
-\r
-                       var doc = this.getDocument();\r
+                               doc = this.getDocument(),\r
+                               body = doc.getBody(),\r
+                               quirks = doc.$.compatMode == 'BackCompat';\r
 \r
                        if ( document.documentElement[ "getBoundingClientRect" ] )\r
                        {\r
@@ -1194,40 +1424,143 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                        return { x : x, y : y };\r
                },\r
 \r
-               scrollIntoView : function( alignTop )\r
+               /**\r
+                * Make any page element visible inside the browser viewport.\r
+                * @param {Boolean} [alignToTop]\r
+                */\r
+               scrollIntoView : function( alignToTop )\r
                {\r
-                       // Get the element window.\r
-                       var win = this.getWindow(),\r
-                               winHeight = win.getViewPaneSize().height;\r
-\r
-                       // Starts from the offset that will be scrolled with the negative value of\r
-                       // the visible window height.\r
-                       var offset = winHeight * -1;\r
-\r
-                       // Append the view pane's height if align to top.\r
-                       // Append element height if we are aligning to the bottom.\r
-                       if ( alignTop )\r
-                               offset += winHeight;\r
-                       else\r
+                       var parent = this.getParent();\r
+                       if ( !parent ) return;\r
+\r
+                       // Scroll the element into parent container from the inner out.\r
+                       do\r
+                       {\r
+                               // Check ancestors that overflows.\r
+                               var overflowed =\r
+                                       parent.$.clientWidth && parent.$.clientWidth < parent.$.scrollWidth\r
+                                       || parent.$.clientHeight && parent.$.clientHeight < parent.$.scrollHeight;\r
+\r
+                               if ( overflowed )\r
+                                       this.scrollIntoParent( parent, alignToTop, 1 );\r
+\r
+                               // Walk across the frame.\r
+                               if ( parent.is( 'html' ) )\r
+                               {\r
+                                       var win = parent.getWindow();\r
+\r
+                                       // Avoid security error.\r
+                                       try\r
+                                       {\r
+                                               var iframe = win.$.frameElement;\r
+                                               iframe && ( parent = new CKEDITOR.dom.element( iframe ) );\r
+                                       }\r
+                                       catch(er){}\r
+                               }\r
+                       }\r
+                       while ( ( parent = parent.getParent() ) );\r
+               },\r
+\r
+               /**\r
+                * Make any page element visible inside one of the ancestors by scrolling the parent.\r
+                * @param {CKEDITOR.dom.element|CKEDITOR.dom.window} parent The container to scroll into.\r
+                * @param {Boolean} [alignToTop] Align the element's top side with the container's\r
+                * when <code>true</code> is specified; align the bottom with viewport bottom when\r
+                * <code>false</code> is specified. Otherwise scroll on either side with the minimum\r
+                * amount to show the element.\r
+                * @param {Boolean} [hscroll] Whether horizontal overflow should be considered.\r
+                */\r
+               scrollIntoParent : function( parent, alignToTop, hscroll )\r
+               {\r
+                       !parent && ( parent = this.getWindow() );\r
+\r
+                       var doc = parent.getDocument();\r
+                       var isQuirks = doc.$.compatMode == 'BackCompat';\r
+\r
+                       // On window <html> is scrolled while quirks scrolls <body>.\r
+                       if ( parent instanceof CKEDITOR.dom.window )\r
+                               parent = isQuirks ? doc.getBody() : doc.getDocumentElement();\r
+\r
+                       // Scroll the parent by the specified amount.\r
+                       function scrollBy( x, y )\r
+                       {\r
+                               // Webkit doesn't support "scrollTop/scrollLeft"\r
+                               // on documentElement/body element.\r
+                               if ( /body|html/.test( parent.getName() ) )\r
+                                       parent.getWindow().$.scrollBy( x, y );\r
+                               else\r
+                               {\r
+                                       parent.$[ 'scrollLeft' ] += x;\r
+                                       parent.$[ 'scrollTop' ] += y;\r
+                               }\r
+                       }\r
+\r
+                       // Figure out the element position relative to the specified window.\r
+                       function screenPos( element, refWin )\r
                        {\r
-                               offset += this.$.offsetHeight || 0;\r
+                               var pos = { x: 0, y: 0 };\r
 \r
-                               // Consider the margin in the scroll, which is ok for our current needs, but\r
-                               // needs investigation if we will be using this function in other places.\r
-                               offset += parseInt( this.getComputedStyle( 'marginBottom' ) || 0, 10 ) || 0;\r
+                               if ( !( element.is( isQuirks ? 'body' : 'html' ) ) )\r
+                               {\r
+                                       var box = element.$.getBoundingClientRect();\r
+                                       pos.x = box.left, pos.y = box.top;\r
+                               }\r
+\r
+                               var win = element.getWindow();\r
+                               if ( !win.equals( refWin ) )\r
+                               {\r
+                                       var outerPos = screenPos( CKEDITOR.dom.element.get( win.$.frameElement ), refWin );\r
+                                       pos.x += outerPos.x, pos.y += outerPos.y;\r
+                               }\r
+\r
+                               return pos;\r
+                       }\r
+\r
+                       // calculated margin size.\r
+                       function margin( element, side )\r
+                       {\r
+                               return parseInt( element.getComputedStyle( 'margin-' + side ) || 0, 10 ) || 0;\r
                        }\r
 \r
-                       // Append the offsets for the entire element hierarchy.\r
-                       var elementPosition = this.getDocumentPosition();\r
-                       offset += elementPosition.y;\r
+                       var win = parent.getWindow();\r
+\r
+                       var thisPos = screenPos( this, win ),\r
+                               parentPos = screenPos( parent, win ),\r
+                               eh = this.$.offsetHeight,\r
+                               ew = this.$.offsetWidth,\r
+                               ch = parent.$.clientHeight,\r
+                               cw = parent.$.clientWidth,\r
+                               lt,\r
+                               br;\r
 \r
-                       // offset value might be out of range(nagative), fix it(#3692).\r
-                       offset = offset < 0 ? 0 : offset;\r
+                       // Left-top margins.\r
+                       lt =\r
+                       {\r
+                               x : thisPos.x - margin( this, 'left' ) - parentPos.x || 0,\r
+                               y : thisPos.y - margin( this, 'top' ) - parentPos.y|| 0\r
+                       };\r
 \r
-                       // Scroll the window to the desired position, if not already visible(#3795).\r
-                       var currentScroll = win.getScrollPosition().y;\r
-                       if ( offset > currentScroll || offset < currentScroll - winHeight )\r
-                               win.$.scrollTo( 0, offset );\r
+                       // Bottom-right margins.\r
+                       br =\r
+                       {\r
+                               x : thisPos.x + ew + margin( this, 'right' ) - ( ( parentPos.x ) + cw ) || 0,\r
+                               y : thisPos.y + eh + margin( this, 'bottom' ) - ( ( parentPos.y ) + ch ) || 0\r
+                       };\r
+\r
+                       // 1. Do the specified alignment as much as possible;\r
+                       // 2. Otherwise be smart to scroll only the minimum amount;\r
+                       // 3. Never cut at the top;\r
+                       // 4. DO NOT scroll when already visible.\r
+                       if ( lt.y < 0 || br.y > 0 )\r
+                       {\r
+                               scrollBy( 0,\r
+                                                 alignToTop === true ? lt.y :\r
+                                                 alignToTop === false ? br.y :\r
+                                                 lt.y < 0 ? lt.y : br.y );\r
+                       }\r
+\r
+                       if ( hscroll && ( lt.x < 0 || br.x > 0 ) )\r
+                               scrollBy( lt.x < 0 ? lt.x : br.x, 0 );\r
                },\r
 \r
                setState : function( state )\r
@@ -1306,16 +1639,22 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                        {\r
                                var attribute = attributes[n];\r
 \r
+                               // Lowercase attribute name hard rule is broken for\r
+                               // some attribute on IE, e.g. CHECKED.\r
+                               var attrName = attribute.nodeName.toLowerCase(),\r
+                                       attrValue;\r
+\r
+                               // We can set the type only once, so do it with the proper value, not copying it.\r
+                               if ( attrName in skipAttributes )\r
+                                       continue;\r
+\r
+                               if ( attrName == 'checked' && ( attrValue = this.getAttribute( attrName ) ) )\r
+                                       dest.setAttribute( attrName, attrValue );\r
                                // IE BUG: value attribute is never specified even if it exists.\r
-                               if ( attribute.specified ||\r
-                                 ( CKEDITOR.env.ie && attribute.nodeValue && attribute.nodeName.toLowerCase() == 'value' ) )\r
+                               else if ( attribute.specified ||\r
+                                 ( CKEDITOR.env.ie && attribute.nodeValue && attrName == 'value' ) )\r
                                {\r
-                                       var attrName = attribute.nodeName;\r
-                                       // We can set the type only once, so do it with the proper value, not copying it.\r
-                                       if ( attrName in skipAttributes )\r
-                                               continue;\r
-\r
-                                       var attrValue = this.getAttribute( attrName );\r
+                                       attrValue = this.getAttribute( attrName );\r
                                        if ( attrValue === null )\r
                                                attrValue = attribute.nodeValue;\r
 \r
@@ -1350,8 +1689,8 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                        this.moveChildren( newNode );\r
 \r
                        // Replace the node.\r
-                       this.$.parentNode.replaceChild( newNode.$, this.$ );\r
-                       newNode.$._cke_expando = this.$._cke_expando;\r
+                       this.getParent() && this.$.parentNode.replaceChild( newNode.$, this.$ );\r
+                       newNode.$[ 'data-cke-expando' ] = this.$[ 'data-cke-expando' ];\r
                        this.$ = newNode.$;\r
                },\r
 \r
@@ -1380,5 +1719,126 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
                getChildCount : function()\r
                {\r
                        return this.$.childNodes.length;\r
-               }\r
+               },\r
+\r
+               disableContextMenu : function()\r
+               {\r
+                       this.on( 'contextmenu', function( event )\r
+                               {\r
+                                       // Cancel the browser context menu.\r
+                                       if ( !event.data.getTarget().hasClass( 'cke_enable_context_menu' ) )\r
+                                               event.data.preventDefault();\r
+                               } );\r
+               },\r
+\r
+               /**\r
+                * Gets element's direction. Supports both CSS 'direction' prop and 'dir' attr.\r
+                */\r
+               getDirection : function( useComputed )\r
+               {\r
+                       return useComputed ?\r
+                               this.getComputedStyle( 'direction' )\r
+                                       // Webkit: offline element returns empty direction (#8053).\r
+                                       || this.getDirection()\r
+                                       || this.getDocument().$.dir\r
+                                       || this.getDocument().getBody().getDirection( 1 )\r
+                               : this.getStyle( 'direction' ) || this.getAttribute( 'dir' );\r
+               },\r
+\r
+               /**\r
+                * Gets, sets and removes custom data to be stored as HTML5 data-* attributes.\r
+                * @param {String} name The name of the attribute, excluding the 'data-' part.\r
+                * @param {String} [value] The value to set. If set to false, the attribute will be removed.\r
+                * @example\r
+                * element.data( 'extra-info', 'test' );   // appended the attribute data-extra-info="test" to the element\r
+                * alert( element.data( 'extra-info' ) );  // "test"\r
+                * element.data( 'extra-info', false );    // remove the data-extra-info attribute from the element\r
+                */\r
+               data : function ( name, value )\r
+               {\r
+                       name = 'data-' + name;\r
+                       if ( value === undefined )\r
+                               return this.getAttribute( name );\r
+                       else if ( value === false )\r
+                               this.removeAttribute( name );\r
+                       else\r
+                               this.setAttribute( name, value );\r
+\r
+                       return null;\r
+               }\r
        });\r
+\r
+       var sides = {\r
+               width : [ "border-left-width", "border-right-width","padding-left", "padding-right" ],\r
+               height : [ "border-top-width", "border-bottom-width", "padding-top",  "padding-bottom" ]\r
+       };\r
+\r
+       // Generate list of specific style rules, applicable to margin/padding/border.\r
+       function expandedRules( style )\r
+       {\r
+               var sides = [ 'top', 'left', 'right', 'bottom' ], components;\r
+\r
+               if ( style == 'border' )\r
+                               components = [ 'color', 'style', 'width' ];\r
+\r
+               var styles = [];\r
+               for ( var i = 0 ; i < sides.length ; i++ )\r
+               {\r
+\r
+                       if ( components )\r
+                       {\r
+                               for ( var j = 0 ; j < components.length ; j++ )\r
+                                       styles.push( [ style, sides[ i ], components[j] ].join( '-' ) );\r
+                       }\r
+                       else\r
+                               styles.push( [ style, sides[ i ] ].join( '-' ) );\r
+               }\r
+\r
+               return styles;\r
+       }\r
+\r
+       function marginAndPaddingSize( type )\r
+       {\r
+               var adjustment = 0;\r
+               for ( var i = 0, len = sides[ type ].length; i < len; i++ )\r
+                       adjustment += parseInt( this.getComputedStyle( sides [ type ][ i ] ) || 0, 10 ) || 0;\r
+               return adjustment;\r
+       }\r
+\r
+       /**\r
+        * Sets the element size considering the box model.\r
+        * @name CKEDITOR.dom.element.prototype.setSize\r
+        * @function\r
+        * @param {String} type The dimension to set. It accepts "width" and "height".\r
+        * @param {Number} size The length unit in px.\r
+        * @param {Boolean} isBorderBox Apply the size based on the border box model.\r
+        */\r
+       CKEDITOR.dom.element.prototype.setSize = function( type, size, isBorderBox )\r
+               {\r
+                       if ( typeof size == 'number' )\r
+                       {\r
+                               if ( isBorderBox && !( CKEDITOR.env.ie && CKEDITOR.env.quirks ) )\r
+                                       size -= marginAndPaddingSize.call( this, type );\r
+\r
+                               this.setStyle( type, size + 'px' );\r
+                       }\r
+               };\r
+\r
+       /**\r
+        * Gets the element size, possibly considering the box model.\r
+        * @name CKEDITOR.dom.element.prototype.getSize\r
+        * @function\r
+        * @param {String} type The dimension to get. It accepts "width" and "height".\r
+        * @param {Boolean} isBorderBox Get the size based on the border box model.\r
+        */\r
+       CKEDITOR.dom.element.prototype.getSize = function( type, isBorderBox )\r
+               {\r
+                       var size = Math.max( this.$[ 'offset' + CKEDITOR.tools.capitalize( type )  ],\r
+                               this.$[ 'client' + CKEDITOR.tools.capitalize( type )  ] ) || 0;\r
+\r
+                       if ( isBorderBox )\r
+                               size -= marginAndPaddingSize.call( this, type );\r
+\r
+                       return size;\r
+               };\r
+})();\r