diff --git a/plugins/clipboard/plugin.js b/plugins/clipboard/plugin.js index 42191cf7079..ea1d52a9df8 100644 --- a/plugins/clipboard/plugin.js +++ b/plugins/clipboard/plugin.js @@ -114,6 +114,8 @@ 'use strict'; ( function() { + var clipboardIdDataType; + // Register the plugin. CKEDITOR.plugins.add( 'clipboard', { requires: 'notification,toolbar', @@ -352,7 +354,6 @@ // events chain. editor.on( 'paste', function( evt ) { var data = evt.data; - if ( data.dataValue ) { editor.insertHtml( data.dataValue, data.type, data.range ); @@ -1515,7 +1516,7 @@ * @readonly * @property {Boolean} */ - isCustomCopyCutSupported: !CKEDITOR.env.ie && !CKEDITOR.env.iOS, + isCustomCopyCutSupported: ( !CKEDITOR.env.ie || CKEDITOR.env.version >= 16 ) && !CKEDITOR.env.iOS, /** * True if the environment supports MIME types and custom data types in dataTransfer/cliboardData getData/setData methods. @@ -1524,7 +1525,7 @@ * @readonly * @property {Boolean} */ - isCustomDataTypesSupported: !CKEDITOR.env.ie, + isCustomDataTypesSupported: !CKEDITOR.env.ie || CKEDITOR.env.version >= 16, /** * True if the environment supports File API. @@ -1583,8 +1584,15 @@ return true; } + // Edge 15 added support for Clipboard API + // (https://wpdev.uservoice.com/forums/257854-microsoft-edge-developer/suggestions/6515107-clipboard-api), however it is + // usable for our case starting from Edge 16 (#468). + if ( CKEDITOR.env.edge && CKEDITOR.env.version >= 16 ) { + return true; + } + // In older Safari and IE HTML data is not available though the Clipboard API. - // In Edge things are a bit messy at the moment - + // In older Edge version things are also a bit messy - // https://connect.microsoft.com/IE/feedback/details/1572456/edge-clipboard-api-text-html-content-messed-up-in-event-clipboarddata // It is safer to use the paste bin in unknown cases. return false; @@ -2013,6 +2021,11 @@ var nativeDataTransfer = evt.data.$ ? evt.data.$.dataTransfer : null, dataTransfer = new this.dataTransfer( nativeDataTransfer, sourceEditor ); + // Set dataTransfer.id only for 'dragstart' event (so for events initializing dataTransfer inside editor) (#962). + if ( evt.name === 'dragstart' ) { + dataTransfer.storeId(); + } + if ( !nativeDataTransfer ) { // No native event. if ( this.dragData ) { @@ -2079,14 +2092,20 @@ */ initPasteDataTransfer: function( evt, sourceEditor ) { if ( !this.isCustomCopyCutSupported ) { - // Edge does not support custom copy/cut, but it have some useful data in the clipboardData (http://dev.ckeditor.com/ticket/13755). + // Edge < 16 does not support custom copy/cut, but it have some useful data in the clipboardData (http://dev.ckeditor.com/ticket/13755). return new this.dataTransfer( ( CKEDITOR.env.edge && evt && evt.data.$ && evt.data.$.clipboardData ) || null, sourceEditor ); } else if ( evt && evt.data && evt.data.$ ) { - var dataTransfer = new this.dataTransfer( evt.data.$.clipboardData, sourceEditor ); + var clipboardData = evt.data.$.clipboardData, + dataTransfer = new this.dataTransfer( clipboardData, sourceEditor ); + + // Set dataTransfer.id only for 'copy'/'cut' events (so for events initializing dataTransfer inside editor) (#962). + if ( evt.name === 'copy' || evt.name === 'cut' ) { + dataTransfer.storeId(); + } if ( this.copyCutData && dataTransfer.id == this.copyCutData.id ) { dataTransfer = this.copyCutData; - dataTransfer.$ = evt.data.$.clipboardData; + dataTransfer.$ = clipboardData; } else { this.copyCutData = dataTransfer; } @@ -2115,7 +2134,8 @@ // so we just read dragged text. // // In Chrome and Firefox we can use custom data types. - var clipboardIdDataType = CKEDITOR.plugins.clipboard.isCustomDataTypesSupported ? 'cke/id' : 'Text'; + clipboardIdDataType = CKEDITOR.plugins.clipboard.isCustomDataTypesSupported ? 'cke/id' : 'Text'; + /** * Facade for the native `dataTransfer`/`clipboadData` object to hide all differences * between browsers. @@ -2152,6 +2172,7 @@ } } }; + this._.fallbackDataTransfer = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( this ); // Check if ID is already created. this.id = this.getData( clipboardIdDataType ); @@ -2169,16 +2190,6 @@ } } - // In IE10+ we can not use any data type besides text, so we do not call setData. - if ( clipboardIdDataType != 'Text' ) { - // Try to set ID so it will be passed from the drag to the drop event. - // On some browsers with some event it is not possible to setData so we - // need to catch exceptions. - try { - this.$.setData( clipboardIdDataType, this.id ); - } catch ( err ) {} - } - if ( editor ) { this.sourceEditor = editor; @@ -2287,17 +2298,28 @@ type = this._.normalizeType( type ); - var data = this._.data[ type ], + var data, result; - if ( isEmpty( data ) ) { + if ( getNative ) { try { - data = this.$.getData( type ); - } catch ( e ) {} - } - - if ( isEmpty( data ) ) { - data = ''; + data = this.$.getData( type ) || ''; + } catch ( e ) { + data = ''; + } + } else { + data = this._.data[ type ] || null; + if ( isEmpty( data ) ) { + if ( this._.fallbackDataTransfer.isRequired() ) { + data = this._.fallbackDataTransfer.getData( type ); + } else { + try { + data = this.$.getData( type ) || ''; + } catch ( e ) { + data = ''; + } + } + } } // Some browsers add at the begging of the HTML data @@ -2353,9 +2375,26 @@ this.id = value; } - try { - this.$.setData( type, value ); - } catch ( e ) {} + if ( this._.fallbackDataTransfer.isRequired() ) { + this._.fallbackDataTransfer.setData( type, value ); + + } else { + try { + this.$.setData( type, value ); + } catch ( e ) {} + } + }, + + /** + * Stores dataTransfer id in native data transfer object + * so it can be retrieved by other events. + * + * @since 4.8.0 + */ + storeId: function() { + if ( clipboardIdDataType !== 'Text' ) { + this.setData( clipboardIdDataType, this.id ); + } }, /** @@ -2393,7 +2432,7 @@ function getAndSetData( type ) { type = that._.normalizeType( type ); - var data = that.getData( type, true ); + var data = that.getData( type, !that._.fallbackDataTransfer.isRequired() ); if ( data ) { that._.data[ type ] = data; } @@ -2481,10 +2520,9 @@ return false; } - // Add custom types. - for ( type in this._.data ) { + CKEDITOR.tools.array.forEach( CKEDITOR.tools.objectKeys( this._.data ), function( type ) { typesToCheck[ type ] = 1; - } + } ); // Add native types. if ( this.$ ) { @@ -2539,6 +2577,307 @@ return undefined; } }; + + /** + * Fallback dataTransfer object which is used together with {@link CKEDITOR.plugins.clipboard.dataTransfer} + * for browsers supporting Clipboard API, but not supporting custom + * MIME types (Edge 16+, see [ckeditor-dev/issues/#962](https://github.com/ckeditor/ckeditor-dev/issues/962)). + * + * @since 4.8.0 + * @class CKEDITOR.plugins.clipboard.fallbackDataTransfer + * @constructor + * @param {CKEDITOR.plugins.clipboard.dataTransfer} dataTransfer DataTransfer + * object which internal cache and + * {@link CKEDITOR.plugins.clipboard.dataTransfer#$ data transfer} objects will be reused. + */ + CKEDITOR.plugins.clipboard.fallbackDataTransfer = function( dataTransfer ) { + /** + * Cache object. Shared with {@link CKEDITOR.plugins.clipboard.dataTransfer} instance. + * + * @private + * @property {Object} _cache + */ + this._cache = dataTransfer._.data; + + /** + * A native dataTransfer object. + * + * @private + * @property {Object} _nativeDataTransfer + */ + this._nativeDataTransfer = dataTransfer.$; + + /** + * A MIME type used for storing custom MIME types. + * + * @private + * @property {String} [_customDataFallbackType='text/html'] + */ + this._customDataFallbackType = 'text/html'; + }; + + /** + * True if the environment supports custom MIME types in {@link CKEDITOR.plugins.clipboard.dataTransfer#getData} + * and {@link CKEDITOR.plugins.clipboard.dataTransfer#setData} methods. + * + * Introduced to distinguish browsers which supports only some whitelisted types (like `text/html`, `application/xml`), + * but does not support custom MIME types (like `cke/id`). When the value of this property equals `null` + * it means it was not yet initialized. + * + * This property should not be accessed directly, use {@link #isRequired} method instead. + * + * @private + * @static + * @property {Boolean} + */ + CKEDITOR.plugins.clipboard.fallbackDataTransfer._isCustomMimeTypeSupported = null; + + /** + * Array containing MIME types which are not supported by native `setData`. Those types are + * recognized by error which is thrown when using native `setData` with a given type + * (see {@link CKEDITOR.plugins.clipboard.fallbackDataTransfer#_isUnsupportedMimeTypeError}). + * + * @private + * @static + * @property {String[]} + */ + CKEDITOR.plugins.clipboard.fallbackDataTransfer._customTypes = []; + + CKEDITOR.plugins.clipboard.fallbackDataTransfer.prototype = { + /** + * Whether {@link CKEDITOR.plugins.clipboard.fallbackDataTransfer fallbackDataTransfer object} should + * be used when operating on native `dataTransfer`. If `true` is returned, it means custom MIME types + * are not supported in the current browser (see {@link #_isCustomMimeTypeSupported}). + * + * @returns {Boolean} + */ + isRequired: function() { + var fallbackDataTransfer = CKEDITOR.plugins.clipboard.fallbackDataTransfer; + + if ( fallbackDataTransfer._isCustomMimeTypeSupported === null ) { + // If there is no `dataTransfer` we cannot detect if fallback is needed. + // Method returns `false` so regular flow will be applied. + if ( !this._nativeDataTransfer ) { + return false; + } else { + var testValue = 'cke test value', + testType = 'cke/mimetypetest'; + + fallbackDataTransfer._isCustomMimeTypeSupported = false; + + try { + this._nativeDataTransfer.setData( testType, testValue ); + fallbackDataTransfer._isCustomMimeTypeSupported = this._nativeDataTransfer.getData( testType ) === testValue; + this._nativeDataTransfer.clearData( testType ); + } catch ( e ) {} + } + } + return !fallbackDataTransfer._isCustomMimeTypeSupported; + }, + + /** + * Returns the data of the given MIME type if stored in a regular way or in a special comment. If given type + * is the same as {@link #_customDataFallbackType} the whole data without special comment is returned. + * + * @param {String} type + * @returns {String} + */ + getData: function( type ) { + // As cache is already checked in CKEDITOR.plugins.clipboard.dataTransfer#getData it is skipped + // here. So the assumption is the given type is not in cache. + + var dataComment = this._extractDataComment( this._getData( this._customDataFallbackType, true ) ), + value = null; + + // If we are getting the same type which may store custom data we need to extract content only. + if ( type === this._customDataFallbackType ) { + value = dataComment.content; + } else { + // If we are getting different type we need to check inside data comment if it is stored there. + if ( dataComment.data && dataComment.data[ type ] ) { + value = dataComment.data[ type ]; + } else { + // And then fallback to regular `getData`. + value = this._getData( type, true ); + } + } + + return value !== null ? value : ''; + }, + + /** + * Sets given data in native `dataTransfer` object. If given MIME type is not supported it uses + * {@link #_customDataFallbackType} MIME type to save data using special comment format: + * + * + * + * It is important to keep in mind that `{ type: value }` object is stringified (using `JSON.stringify`) + * and encoded (using `encodeURIComponent`). + * + * @param {String} type + * @param {String} value + */ + setData: function( type, value ) { + // In case of fallbackDataTransfer, cache does not reflect native data one-to-one. For example, having + // types like text/plain, text/html, cke/id will result in cache storing: + // + // { + // text/plain: value1, + // text/html: value2, + // cke/id: value3 + // } + // + // and native dataTransfer storing: + // + // { + // text/plain: value1, + // text/html: value2 + // } + // + // This way, accessing cache will always return proper value for a given type without a need for further processing. + // Cache is already set in CKEDITOR.plugins.clipboard.dataTransfer#setData so it is skipped here. + + if ( type === this._customDataFallbackType ) { + value = this._applyDataComment( value, this._getFallbackTypeData() ); + } + + try { + this._nativeDataTransfer.setData( type, value ); + } catch ( e ) { + if ( this._isUnsupportedMimeTypeError( e ) ) { + var fallbackDataTransfer = CKEDITOR.plugins.clipboard.fallbackDataTransfer; + + if ( CKEDITOR.tools.indexOf( fallbackDataTransfer._customTypes, type ) === -1 ) { + fallbackDataTransfer._customTypes.push( type ); + } + + var fallbackTypeContent = this._getFallbackTypeContent(), + fallbackTypeData = this._getFallbackTypeData(); + + fallbackTypeData[ type ] = value; + + try { + this._nativeDataTransfer.setData( this._customDataFallbackType, + this._applyDataComment( fallbackTypeContent, fallbackTypeData ) ); + } catch ( e ) { + // Some dev logger should be added here. + } + } + } + }, + + /** + * Native getData wrapper. + * + * @private + * @param {String} type + * @param {Boolean} [skipCache=false] + * @returns {String|null} + */ + _getData: function( type, skipCache ) { + if ( !skipCache && this._cache[ type ] ) { + return this._cache[ type ]; + } else { + try { + return this._nativeDataTransfer.getData( type ); + } catch ( e ) { + return null; + } + } + }, + + /** + * Returns content stored in {@link #\_customDataFallbackType}. Content is always first retrieved + * from {@link #_cache} and then from native `dataTransfer` object. + * + * @private + * @returns {String} + */ + _getFallbackTypeContent: function() { + var fallbackTypeContent = this._cache[ this._customDataFallbackType ]; + + if ( !fallbackTypeContent ) { + fallbackTypeContent = this._extractDataComment( this._getData( this._customDataFallbackType, true ) ).content; + } + return fallbackTypeContent; + }, + + /** + * Returns custom data stored in {@link #\_customDataFallbackType}. Custom data is always first retrieved + * from {@link #_cache} and then from native `dataTransfer` object. + * + * @private + * @returns {Object} + */ + _getFallbackTypeData: function() { + var fallbackTypes = CKEDITOR.plugins.clipboard.fallbackDataTransfer._customTypes, + fallbackTypeData = this._extractDataComment( this._getData( this._customDataFallbackType, true ) ).data || {}; + + CKEDITOR.tools.array.forEach( fallbackTypes, function( type ) { + fallbackTypeData[ type ] = this._cache[ type ] !== undefined ? this._cache[ type ] : fallbackTypeData[ type ]; + }, this ); + return fallbackTypeData; + }, + + /** + * Whether provided error means that unsupported MIME type was used when calling native `dataTransfer.setData` method. + * + * @private + * @param {Error} error + * @returns {Boolean} + */ + _isUnsupportedMimeTypeError: function( error ) { + return error.message && error.message.search( /element not found/gi ) !== -1; + }, + + /** + * Extracts `cke-data` comment from the given content. + * + * @private + * @param {String} content + * @returns {Object} Returns an object containing extracted data as `data` + * and content (without `cke-data` comment) as `content`. + * @returns {Object|null} return.data Object containing `MIME type : value` pairs + * or null if `cke-data` comment is not present. + * @returns {String} return.content Regular content without `cke-data` comment. + */ + _extractDataComment: function( content ) { + var result = { + data: null, + content: content || '' + }; + + // At least 17 characters length: . + if ( content && content.length > 16 ) { + var matcher = //g, + matches; + + matches = matcher.exec( content ); + if ( matches && matches[ 1 ] ) { + result.data = JSON.parse( decodeURIComponent( matches[ 1 ] ) ); + result.content = content.replace( matches[ 0 ], '' ); + } + } + return result; + }, + + /** + * Creates `cke-data` comment containing stringified and encoded data object which is prepended to a given content. + * + * @private + * @param {String} content + * @param {Object} data + * @returns {String} + */ + _applyDataComment: function( content, data ) { + var customData = ''; + if ( data && CKEDITOR.tools.objectKeys( data ).length ) { + customData = ''; + } + return customData + ( content && content.length ? content : '' ); + } + }; + } )(); /** diff --git a/tests/_benderjs/ckeditor/static/tools.js b/tests/_benderjs/ckeditor/static/tools.js index 0651c7a6f10..f0175c67379 100644 --- a/tests/_benderjs/ckeditor/static/tools.js +++ b/tests/_benderjs/ckeditor/static/tools.js @@ -816,13 +816,26 @@ types: [], files: CKEDITOR.env.ie && CKEDITOR.env.version < 10 ? undefined : [], _data: {}, - // Emulate browsers native behavior for getDeta/setData. + // Emulate browsers native behavior for getData/setData. setData: function( type, data ) { - if ( CKEDITOR.env.ie && type != 'Text' && type != 'URL' ) + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 16 && type != 'Text' && type != 'URL' ) { throw 'Unexpected call to method or property access.'; + } - if ( CKEDITOR.env.ie && CKEDITOR.env.version > 9 && type == 'URL' ) + if ( CKEDITOR.env.ie && CKEDITOR.env.version > 9 && type == 'URL' ) { return; + } + + // While Edge 16+ supports Clipboard API, it does not support custom mime types + // in `setData` and throws `Element not found.` if such are used. + if ( CKEDITOR.env.edge && CKEDITOR.env.version >= 16 && + CKEDITOR.tools.indexOf( [ 'Text', 'URL', 'text/plain', 'text/html', 'application/xml' ], type ) === -1 ) { + + throw { + name: 'Error', + message: 'Element not found.' + }; + } if ( type == 'text/plain' || type == 'Text' ) { this._data[ 'text/plain' ] = data; @@ -834,13 +847,23 @@ this.types.push( type ); }, getData: function( type ) { - if ( CKEDITOR.env.ie && type != 'Text' && type != 'URL' ) + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 16 && type != 'Text' && type != 'URL' ) { throw 'Invalid argument.'; + } - if ( typeof this._data[ type ] === 'undefined' || this._data[ type ] === null ) + if ( typeof this._data[ type ] === 'undefined' || this._data[ type ] === null ) { return ''; + } return this._data[ type ]; + }, + clearData: function( type ) { + var index = CKEDITOR.tools.indexOf( this.types, type ); + + if ( index !== -1 ) { + delete this._data[ type ]; + this.types.splice( index, 1 ); + } } }; }, @@ -884,7 +907,7 @@ return { $: { ctrlKey: true, - clipboardData: CKEDITOR.env.ie ? undefined : dataTransfer + clipboardData: ( CKEDITOR.env.ie && CKEDITOR.env.version < 16 ) ? undefined : dataTransfer }, preventDefault: function() { // noop diff --git a/tests/plugins/clipboard/datatransfer.js b/tests/plugins/clipboard/datatransfer.js index 4796b931739..4a7fe10af0c 100644 --- a/tests/plugins/clipboard/datatransfer.js +++ b/tests/plugins/clipboard/datatransfer.js @@ -36,21 +36,34 @@ bender.test( { 'test id': function() { var nativeData1 = bender.tools.mockNativeDataTransfer(), nativeData2 = bender.tools.mockNativeDataTransfer(), - dataTransfer1a = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData1 ), - dataTransfer1b = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData1 ), - dataTransfer2 = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData2 ); + dataTransfer1a, + dataTransfer1b, + dataTransfer2; + + // Setting id was moved from dataTransfer constructor to functions which initializes dataTransfer object + // only on specific events so we need to simulate these behaviour here too (#962). + dataTransfer1a = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData1 ); + dataTransfer1a.storeId(); + + dataTransfer1b = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData1 ); + dataTransfer1b.storeId(); + + dataTransfer2 = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData2 ); + dataTransfer2.storeId(); assert.areSame( dataTransfer1a.id, dataTransfer1b.id, 'Ids for object based on the same event should be the same.' ); // In IE we can not use any data type besides text, so id is fixed. - if ( CKEDITOR.plugins.clipboard.isCustomDataTypesSupported ) + if ( CKEDITOR.plugins.clipboard.isCustomDataTypesSupported ) { assert.areNotSame( dataTransfer1a.id, dataTransfer2.id, 'Ids for object based on different events should be different.' ); + } }, 'test internal drag drop': function() { var bot = this.editorBots.editor1, editor = this.editors.editor1, - nativeData, dataTransfer; + nativeData, + dataTransfer; bot.setHtmlWithSelection( '
x[xfoox]x
' ); @@ -89,7 +102,8 @@ bender.test( { 'test drop text from external source': function() { var editor = this.editors.editor1, - nativeData, dataTransfer; + nativeData, + dataTransfer; nativeData = bender.tools.mockNativeDataTransfer(); nativeData.setData( 'Text', 'xfoox' ); @@ -108,7 +122,8 @@ bender.test( { 'test drop html from external source': function() { var isCustomDataTypesSupported = CKEDITOR.plugins.clipboard.isCustomDataTypesSupported, editor = this.editors.editor1, - nativeData, dataTransfer; + nativeData, + dataTransfer; nativeData = bender.tools.mockNativeDataTransfer(); nativeData.setData( 'Text', 'bar' ); @@ -131,7 +146,8 @@ bender.test( { var bot1 = this.editorBots.editor1, editor1 = this.editors.editor1, editor2 = this.editors.editor2, - nativeData, dataTransfer; + nativeData, + dataTransfer; bot1.setHtmlWithSelection( 'x[xfoox]x
' ); @@ -477,12 +493,42 @@ bender.test( { assert.areSame( html, dataTransfer.getData( 'text/html', true ) ); }, + 'test getData with getNative flag if cache differs from native data': function() { + if ( !CKEDITOR.plugins.clipboard.isCustomDataTypesSupported ) { + return assert.ignore(); + } + + var html = '' + + '' + + '' + + '' + + '' + + '' + + '' + + 'Foo
' + + 'Bar
' + + '' + + '', + newHtml = html.replace( 'Bar', 'Baz' ), + nativeData = bender.tools.mockNativeDataTransfer(), + dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData ); + + nativeData.setData( 'text/html', html ); + dataTransfer.cacheData(); + nativeData.setData( 'text/html', newHtml ); + + assert.areSame( newHtml, dataTransfer.getData( 'text/html', true ) ); + }, + 'test cacheData': function() { var isCustomDataTypesSupported = CKEDITOR.plugins.clipboard.isCustomDataTypesSupported, // Emulate native clipboard. nativeData = bender.tools.mockNativeDataTransfer(); - if ( isCustomDataTypesSupported ) { + // This test uses mocked `setData` which does not applies fallback + // for Edge >= 16 (because it skips `CKEDITOR.plugins.clipboard.dataTransfer` wrapper) + // so it works as if `isCustomDataTypesSupported` flag was turned off for Edge (#962). + if ( isCustomDataTypesSupported && !CKEDITOR.env.edge ) { nativeData.setData( 'text/html', 'foo' ); nativeData.setData( 'text/plain', 'bom' ); nativeData.setData( 'cke/custom', 'bar' ); @@ -501,8 +547,8 @@ bender.test( { nativeData.setData = throwPermissionDenied; nativeData.getData = throwPermissionDenied; - // Assert - if ( isCustomDataTypesSupported ) { + // Assert. Edge browser case same as above (#962). + if ( isCustomDataTypesSupported && !CKEDITOR.env.edge ) { assert.areSame( 'foo', dataTransfer.getData( 'text/html' ) ); assert.areSame( 'bom', dataTransfer.getData( 'text/plain' ) ); assert.areSame( 'bar', dataTransfer.getData( 'cke/custom' ) ); @@ -553,10 +599,6 @@ bender.test( { // http://dev.ckeditor.com/ticket/12961 'test file in items': function() { - if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) { - assert.ignore(); - } - var nativeData = bender.tools.mockNativeDataTransfer(), dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData ), file = { type: 'type' }; @@ -642,10 +684,6 @@ bender.test( { // http://dev.ckeditor.com/ticket/12961 'test file in items with cache': function() { - if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) { - assert.ignore(); - } - var nativeData = bender.tools.mockNativeDataTransfer(), dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData ), file = { type: 'type' }; @@ -688,7 +726,6 @@ bender.test( { nativeData.files.push( 'foo' ); - // debugger; dataTransfer.cacheData(); assert.areSame( 1, dataTransfer.getFilesCount() ); @@ -813,9 +850,9 @@ bender.test( { 'test initDragDataTransfer binding': function() { var nativeData1 = bender.tools.mockNativeDataTransfer(), nativeData2 = bender.tools.mockNativeDataTransfer(), - evt1a = { data: { $: { dataTransfer: nativeData1 } } }, - evt1b = { data: { $: { dataTransfer: nativeData1 } } }, - evt2 = { data: { $: { dataTransfer: nativeData2 } } }; + evt1a = { data: { $: { dataTransfer: nativeData1 } }, name: 'dragstart' }, + evt1b = { data: { $: { dataTransfer: nativeData1 } }, name: 'dragstart' }, + evt2 = { data: { $: { dataTransfer: nativeData2 } }, name: 'dragstart' }; CKEDITOR.plugins.clipboard.initDragDataTransfer( evt1a ); CKEDITOR.plugins.clipboard.initDragDataTransfer( evt1b ); @@ -892,9 +929,9 @@ bender.test( { var nativeData1 = bender.tools.mockNativeDataTransfer(), nativeData2 = bender.tools.mockNativeDataTransfer(), - evt1 = { data: { $: { clipboardData: nativeData1 } } }, - evt2 = { data: { $: { clipboardData: nativeData1 } } }, - evt3 = { data: { $: { clipboardData: nativeData2 } } }, + evt1 = { data: { $: { clipboardData: nativeData1 } }, name: 'copy' }, + evt2 = { data: { $: { clipboardData: nativeData1 } }, name: 'copy' }, + evt3 = { data: { $: { clipboardData: nativeData2 } }, name: 'copy' }, dataTransfer1 = CKEDITOR.plugins.clipboard.initPasteDataTransfer( evt1 ), dataTransfer2 = CKEDITOR.plugins.clipboard.initPasteDataTransfer( evt2 ), dataTransfer3 = CKEDITOR.plugins.clipboard.initPasteDataTransfer( evt3 ); @@ -950,5 +987,103 @@ bender.test( { text: isCustomDataTypesSupported ? 'xfoox' : '', html: 'xfoox' }, dataTransfer ); + }, + + // (#962) + 'test new dataTransfer id is created for copy/cut/dragstart events': function() { + if ( !CKEDITOR.plugins.clipboard.isCustomCopyCutSupported ) { + assert.ignore(); + } + + var nativeData1 = bender.tools.mockNativeDataTransfer(), + nativeData2 = bender.tools.mockNativeDataTransfer(), + nativeData3 = bender.tools.mockNativeDataTransfer(), + evt1 = { data: { $: { clipboardData: nativeData1 } }, name: 'copy' }, + evt2 = { data: { $: { clipboardData: nativeData2 } }, name: 'cut' }, + evt3 = { data: { $: { dataTransfer: nativeData3 } }, name: 'dragstart' }, + dataTransfer1 = CKEDITOR.plugins.clipboard.initPasteDataTransfer( evt1 ), + dataTransfer2 = CKEDITOR.plugins.clipboard.initPasteDataTransfer( evt2 ), + dtFallback1 = dataTransfer1._.fallbackDataTransfer, + dtFallback2 = dataTransfer2._.fallbackDataTransfer, + dataTransfer3, + dtFallback3; + + CKEDITOR.plugins.clipboard.initDragDataTransfer( evt3 ); + dataTransfer3 = evt3.data.dataTransfer; + dtFallback3 = dataTransfer3._.fallbackDataTransfer; + + // Check if ids are not empty. + assert.isTrue( dataTransfer1.id.length > 0, 'dataTransfer1 id is not empty' ); + assert.isTrue( dataTransfer2.id.length > 0, 'dataTransfer2 id is not empty' ); + assert.isTrue( dataTransfer3.id.length > 0, 'dataTransfer3 id is not empty' ); + + if ( CKEDITOR.plugins.clipboard.isCustomDataTypesSupported && !CKEDITOR.env.edge ) { + assert.areSame( dataTransfer1.id, nativeData1.getData( 'cke/id' ), 'cke/id type holds dataTransfer1 id' ); + assert.areSame( dataTransfer2.id, nativeData2.getData( 'cke/id' ), 'cke/id type holds dataTransfer2 id' ); + assert.areSame( dataTransfer3.id, nativeData3.getData( 'cke/id' ), 'cke/id type holds dataTransfer3 id' ); + } else { + assert.areSame( dataTransfer1.id, + dtFallback1._extractDataComment( nativeData1.getData( dtFallback1._customDataFallbackType ) ).data[ 'cke/id' ], + 'cke/id custom data holds dataTransfer1 id' ); + + assert.areSame( dataTransfer2.id, + dtFallback2._extractDataComment( nativeData2.getData( dtFallback2._customDataFallbackType ) ).data[ 'cke/id' ], + 'cke/id custom data holds dataTransfer2 id' ); + + assert.areSame( dataTransfer3.id, + dtFallback3._extractDataComment( nativeData3.getData( dtFallback3._customDataFallbackType ) ).data[ 'cke/id' ], + 'cke/id custom data holds dataTransfer3 id' ); + } + }, + + // (#962) + 'test no new dataTransfer id is created for paste/drop/dragend events': function() { + if ( !CKEDITOR.plugins.clipboard.isCustomCopyCutSupported ) { + assert.ignore(); + } + + var nativeData1 = bender.tools.mockNativeDataTransfer(), + nativeData2 = bender.tools.mockNativeDataTransfer(), + nativeData3 = bender.tools.mockNativeDataTransfer(), + evt1 = { data: { $: { clipboardData: nativeData1 } }, name: 'paste' }, + evt2 = { data: { $: { dataTransfer: nativeData2 } }, name: 'drop' }, + evt3 = { data: { $: { dataTransfer: nativeData3 } }, name: 'dragend' }, + dataTransfer1 = CKEDITOR.plugins.clipboard.initPasteDataTransfer( evt1 ), + dataTransfer2, + dataTransfer3; + + CKEDITOR.plugins.clipboard.initDragDataTransfer( evt2 ); + dataTransfer2 = evt2.data.dataTransfer; + + CKEDITOR.plugins.clipboard.initDragDataTransfer( evt3 ); + dataTransfer3 = evt3.data.dataTransfer; + + if ( CKEDITOR.plugins.clipboard.isCustomDataTypesSupported && !CKEDITOR.env.edge ) { + assert.areSame( '', nativeData1.getData( 'cke/id' ), 'dataTransfer1 id is empty' ); + assert.areSame( '', nativeData2.getData( 'cke/id' ), 'dataTransfer2 id is empty' ); + assert.areSame( '', nativeData3.getData( 'cke/id' ), 'dataTransfer2 id is empty' ); + } else { + // As dataTransfer id is stored in `customDataFallbackType` ('text/html' mime type), we just check if it is empty. + assert.areSame( '', + nativeData1.getData( dataTransfer1._.fallbackDataTransfer._customDataFallbackType ), 'dataTransfer1 id is empty' ); + assert.areSame( '', + nativeData2.getData( dataTransfer2._.fallbackDataTransfer._customDataFallbackType ), 'dataTransfer2 id is empty' ); + assert.areSame( '', + nativeData2.getData( dataTransfer3._.fallbackDataTransfer._customDataFallbackType ), 'dataTransfer2 id is empty' ); + } + }, + + 'test if cache is initialized on dataTransfer creation': function() { + var cache = new CKEDITOR.plugins.clipboard.dataTransfer()._.data; + + assert.isObject( cache, 'cache should be initialized' ); + }, + + 'test if different dataTransfer objects has different caches': function() { + var dt1 = new CKEDITOR.plugins.clipboard.dataTransfer(), + dt2 = new CKEDITOR.plugins.clipboard.dataTransfer(); + + assert.isObject( dt1._.data, 'cache should be initialized' ); + assert.isTrue( dt1._.data !== dt2._.data, 'caches should not be equal' ); } } ); diff --git a/tests/plugins/clipboard/fallbackdatatransfer.html b/tests/plugins/clipboard/fallbackdatatransfer.html new file mode 100644 index 00000000000..ff26cc1a1fd --- /dev/null +++ b/tests/plugins/clipboard/fallbackdatatransfer.html @@ -0,0 +1,33 @@ +Test1
+Test1
+Test1
+Test1
+ +foobar
+html text
' ); + + assert.areSame( 'plain text', getDataNoCache( dataTransfer, 'text/plain' ) ); + assert.areSame( 'html text
', getDataNoCache( dataTransfer, 'text/html' ) ); + + this.assertDataTransferType( dataTransfer, 'text/plain', 'plain text' ); + this.assertDataTransferType( dataTransfer, 'text/html', 'html text
', { 'cke/id': dataTransfer.id } ); + }, + + 'test setData/getData with predefined type - fallbackDataTransfer': function() { + var nativeData = bender.tools.mockNativeDataTransfer(), + dataTransferFallback = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData )._.fallbackDataTransfer; + + dataTransferFallback.setData( 'text/plain', 'plain text' ); + dataTransferFallback.setData( 'text/html', 'html text
' ); + + assert.areSame( 'plain text', dataTransferFallback.getData( 'text/plain' ) ); + assert.areSame( 'html text
', dataTransferFallback.getData( 'text/html' ) ); + }, + + 'test setData/getData with custom type - dataTransfer': function() { + var nativeData = bender.tools.mockNativeDataTransfer(), + eventMock = { data: { $: { clipboardData: nativeData } }, name: 'copy' }, + dataTransfer = CKEDITOR.plugins.clipboard.initPasteDataTransfer( eventMock ); + + dataTransfer.setData( 'cke/custom', 'cke-custom data' ); + dataTransfer.setData( 'custom/tag', 'custom html tag
' ); + + assert.areSame( 'cke-custom data', getDataNoCache( dataTransfer, 'cke/custom' ) ); + assert.areSame( 'custom html tag
', getDataNoCache( dataTransfer, 'custom/tag' ) ); + + this.assertDataTransferType( dataTransfer, 'text/html', '', { + 'cke/id': dataTransfer.id, + 'cke/custom': 'cke-custom data', + 'custom/tag': 'custom html tag
' + } ); + }, + + 'test setData/getData with custom type - fallbackDataTransfer': function() { + var nativeData = bender.tools.mockNativeDataTransfer(), + dataTransferFallback = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData )._.fallbackDataTransfer; + + dataTransferFallback.setData( 'cke/custom', 'cke-custom data' ); + dataTransferFallback.setData( 'custom/tag', 'custom html tag
' ); + + assert.areSame( 'cke-custom data', dataTransferFallback.getData( 'cke/custom' ) ); + assert.areSame( 'custom html tag
', dataTransferFallback.getData( 'custom/tag' ) ); + + this.assertDataTransferType( dataTransferFallback, 'text/html', '', { + 'cke/custom': 'cke-custom data', + 'custom/tag': 'custom html tag
' + } ); + }, + + 'test setData with custom type does not affect getData( "text/html" ) - dataTransfer': function() { + var nativeData = bender.tools.mockNativeDataTransfer(), + eventMock = { data: { $: { clipboardData: nativeData } }, name: 'copy' }, + dataTransfer = CKEDITOR.plugins.clipboard.initPasteDataTransfer( eventMock ); + + dataTransfer.setData( 'text/html', 'custom html tag
' ); + + assert.areSame( getDataNoCache( dataTransfer, 'cke/custom' ), 'cke-custom data' ); + assert.areSame( getDataNoCache( dataTransfer, 'custom/tag' ), 'custom html tag
' ); + this.assertDataTransferType( dataTransfer, 'text/html', '', { + 'cke/id': dataTransfer.id, + 'cke/custom': 'cke-custom data', + 'custom/tag': 'custom html tag
' + } ); + + dataTransfer.setData( 'cke/custom', 'cke-custom' ); + + assert.areSame( getDataNoCache( dataTransfer, 'cke/custom' ), 'cke-custom' ); + assert.areSame( getDataNoCache( dataTransfer, 'custom/tag' ), 'custom html tag
' ); + this.assertDataTransferType( dataTransfer, 'text/html', '', { + 'cke/id': dataTransfer.id, + 'cke/custom': 'cke-custom', + 'custom/tag': 'custom html tag
' + } ); + }, + + 'test setting same custom type overwrites the previous value and does not affect other types - fallbackDataTransfer': function() { + var nativeData = bender.tools.mockNativeDataTransfer(), + dataTransferFallback = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData )._.fallbackDataTransfer; + + dataTransferFallback.setData( 'cke/custom', 'cke-custom data' ); + dataTransferFallback.setData( 'custom/tag', 'custom html tag
' ); + + assert.areSame( dataTransferFallback.getData( 'cke/custom' ), 'cke-custom data' ); + assert.areSame( dataTransferFallback.getData( 'custom/tag' ), 'custom html tag
' ); + this.assertDataTransferType( dataTransferFallback, 'text/html', '', { + 'cke/custom': 'cke-custom data', + 'custom/tag': 'custom html tag
' + } ); + + dataTransferFallback.setData( 'cke/custom', 'cke-custom' ); + + assert.areSame( dataTransferFallback.getData( 'cke/custom' ), 'cke-custom' ); + assert.areSame( dataTransferFallback.getData( 'custom/tag' ), 'custom html tag
' ); + this.assertDataTransferType( dataTransferFallback, 'text/html', '', { + 'cke/custom': 'cke-custom', + 'custom/tag': 'custom html tag
' + } ); + }, + + 'test getting "text/html" and "cke/test" from cache': function() { + var nativeData = bender.tools.mockNativeDataTransfer(), + eventMock = { data: { $: { clipboardData: nativeData } }, name: 'copy' }, + dataTransfer = CKEDITOR.plugins.clipboard.initPasteDataTransfer( eventMock ); + + dataTransfer.setData( 'text/html', 'html text
' ); + dataTransfer.setData( 'cke/test', 'cke_test' ); + + assert.areSame( 'html text
', dataTransfer.getData( 'text/html' ) ); + assert.areSame( 'cke_test', dataTransfer.getData( 'cke/test' ) ); + }, + + 'test _applyDataComment case1': function() { + var dataTransferFallback = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { _: { data: {} } } ), + expected = document.querySelector( '#case1' ).innerHTML; + + this.assertApplyDataComment( 'Test1
', { test: 1 }, dataTransferFallback, expected ); + }, + + 'test _applyDataComment case2': function() { + var dataTransferFallback = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { _: { data: {} } } ), + expected = document.querySelector( '#case2' ).innerHTML; + + this.assertApplyDataComment( 'Test1
', { test: 1, comment: '' }, dataTransferFallback, expected ); + }, + + 'test _applyDataComment case3': function() { + var dataTransferFallback = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { _: { data: {} } } ), + expected = CKEDITOR.tools.trim( document.querySelector( '#case3' ).innerHTML ); + + this.assertApplyDataComment( 'Test1
', { test: 1 }, dataTransferFallback, expected ); + }, + + 'test _applyDataComment case4': function() { + var dataTransferFallback = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { _: { data: {} } } ), + expected = CKEDITOR.tools.trim( document.querySelector( '#case4' ).innerHTML ); + + this.assertApplyDataComment( 'Test1
', { test: 123 }, dataTransferFallback, expected ); + }, + + 'test _applyDataComment with empty content': function() { + var dataTransferFallback = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { _: { data: {} } } ), + expected = CKEDITOR.tools.trim( document.querySelector( '#empty-content' ).innerHTML ); + + this.assertApplyDataComment( undefined, { test: 1 }, dataTransferFallback, expected ); + this.assertApplyDataComment( null, { test: 1 }, dataTransferFallback, expected ); + this.assertApplyDataComment( '', { test: 1 }, dataTransferFallback, expected ); + }, + + 'test _applyDataComment with empty data': function() { + var dataTransferFallback = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { _: { data: {} } } ), + expected = CKEDITOR.tools.trim( document.querySelector( '#empty-data' ).innerHTML ); + + this.assertApplyDataComment( 'foobar
', '', dataTransferFallback, expected ); + this.assertApplyDataComment( 'foobar
', null, dataTransferFallback, expected ); + this.assertApplyDataComment( 'foobar
', {}, dataTransferFallback, expected ); + }, + + 'test _applyDataComment with empty content and data': function() { + var dataTransferFallback = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { _: { data: {} } } ), + expected = ''; + + this.assertApplyDataComment( undefined, null, dataTransferFallback, expected ); + this.assertApplyDataComment( null, undefined, dataTransferFallback, expected ); + this.assertApplyDataComment( '', {}, dataTransferFallback, expected ); + }, + + 'test _extractDataComment case1': function() { + var fallbackDataTransfer = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { _: { data: {} } } ), + content = document.querySelector( '#case1' ).innerHTML, + extracted = fallbackDataTransfer._extractDataComment( content ); + + objectAssert.areEqual( extracted.data, { test: 1 } ); + assert.isInnerHtmlMatching( 'Test1
', extracted.content ); + }, + + 'test _extractDataComment case2': function() { + var fallbackDataTransfer = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { _: { data: {} } } ), + content = document.querySelector( '#case2' ).innerHTML, + extracted = fallbackDataTransfer._extractDataComment( content ); + + objectAssert.areEqual( extracted.data, { test: 1, comment: '' } ); + assert.isInnerHtmlMatching( 'Test1
', extracted.content ); + }, + + 'test _extractDataComment case3': function() { + var fallbackDataTransfer = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { _: { data: {} } } ), + content = document.querySelector( '#case3' ).innerHTML, + extracted = fallbackDataTransfer._extractDataComment( content ); + + objectAssert.areEqual( extracted.data, { test: 1 } ); + assert.isInnerHtmlMatching( 'Test1
', extracted.content ); + }, + + 'test _extractDataComment case4': function() { + var fallbackDataTransfer = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { _: { data: {} } } ), + content = document.querySelector( '#case4' ).innerHTML, + extracted = fallbackDataTransfer._extractDataComment( content ); + + objectAssert.areEqual( extracted.data, { test: 123 } ); + assert.isInnerHtmlMatching( 'Test1
', extracted.content ); + }, + + 'test _extractDataComment with empty content': function() { + var fallbackDataTransfer = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { _: { data: {} } } ), + content = CKEDITOR.tools.trim( document.querySelector( '#empty-content' ).innerHTML ), + extracted = fallbackDataTransfer._extractDataComment( content ); + + objectAssert.areEqual( extracted.data, { test: 1 } ); + assert.areSame( '', extracted.content ); + }, + + 'test _extractDataComment with empty data': function() { + var fallbackDataTransfer = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { _: { data: {} } } ), + content = CKEDITOR.tools.trim( document.querySelector( '#empty-data' ).innerHTML ), + extracted = fallbackDataTransfer._extractDataComment( content ); + + assert.isNull( extracted.data ); + assert.isInnerHtmlMatching( 'foobar
', extracted.content ); + }, + + 'test _extractDataComment with empty data and content': function() { + var fallbackDataTransfer = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { _: { data: {} } } ), + extracted = fallbackDataTransfer._extractDataComment( '' ); + + assert.isNull( extracted.data ); + assert.areSame( '', extracted.content ); + }, + + 'test _extractDataComment with falsy value': function() { + var fallbackDataTransfer = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { _: { data: {} } } ); + + assert.areSame( '', fallbackDataTransfer._extractDataComment( '' ).content ); + assert.areSame( '', fallbackDataTransfer._extractDataComment( null ).content ); + assert.areSame( '', fallbackDataTransfer._extractDataComment( undefined ).content ); + assert.areSame( '', fallbackDataTransfer._extractDataComment( false ).content ); + }, + + 'test if isRequired sets _isCustomMimeTypeSupported flag on the first run': function() { + var fallbackDataTransfer = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( + { $: bender.tools.mockNativeDataTransfer(), _: { data: {} } } ); + + CKEDITOR.plugins.clipboard.fallbackDataTransfer._isCustomMimeTypeSupported = null; + + var isRequiredValue = fallbackDataTransfer.isRequired(), + flagValue = CKEDITOR.plugins.clipboard.fallbackDataTransfer._isCustomMimeTypeSupported; + + assert.isTrue( flagValue !== null, '_isCustomMimeTypeSupported should be set' ); + assert.isTrue( flagValue === false || flagValue === true, '_isCustomMimeTypeSupported should be only true or false' ); + assert.isTrue( flagValue !== isRequiredValue, 'isRequired should return value equal to !_isCustomMimeTypeSupported' ); + }, + + 'test if isRequired clears test MIME type': function() { + var nativeData = bender.tools.mockNativeDataTransfer(), + fallbackDataTransfer = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { $: nativeData, _: { data: {} } } ); + + CKEDITOR.plugins.clipboard.fallbackDataTransfer._isCustomMimeTypeSupported = null; + + fallbackDataTransfer.isRequired(); + + assert.isTrue( nativeData.types.length === 0, 'dataTransfer.types should be empty' ); + assert.isTrue( CKEDITOR.tools.objectKeys( nativeData._data ).length === 0, 'dataTransfer should be empty' ); + }, + + 'test if isRequired does not remove other MIME types': function() { + var nativeData = bender.tools.mockNativeDataTransfer(), + fallbackDataTransfer = new CKEDITOR.plugins.clipboard.fallbackDataTransfer( { $: nativeData, _: { data: {} } } ); + + CKEDITOR.plugins.clipboard.fallbackDataTransfer._isCustomMimeTypeSupported = null; + + nativeData.setData( 'text/html', 'foobar' ); + + fallbackDataTransfer.isRequired(); + + assert.areSame( 1, nativeData.types.length, 'dataTransfer.types should only contain one type' ); + assert.areSame( 1, CKEDITOR.tools.objectKeys( nativeData._data ).length, 'dataTransfer should only contain one type' ); + arrayAssert.itemsAreEqual( CKEDITOR.tools.objectKeys( nativeData._data ), [ 'text/html' ], 'dataTransfer should only contain text/html' ); + }, + + 'test getFallbackTypeContent prioritize cache': function() { + var nativeData = bender.tools.mockNativeDataTransfer(), + dataTransferFallback = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData )._.fallbackDataTransfer; + + dataTransferFallback._cache[ dataTransferFallback._customDataFallbackType ] = 'cache value'; + nativeData.setData( dataTransferFallback._customDataFallbackType, 'native value' ); + + assert.areEqual( 'cache value', dataTransferFallback._getFallbackTypeContent() ); + }, + + 'test getFallbackTypeContent fallbacks to native data': function() { + var nativeData = bender.tools.mockNativeDataTransfer(), + dataTransferFallback = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData )._.fallbackDataTransfer; + + nativeData.setData( dataTransferFallback._customDataFallbackType, 'native value' ); + + assert.areEqual( 'native value', dataTransferFallback._getFallbackTypeContent() ); + }, + + 'test getFallbackTypeContent for empty content in both cache and native data': function() { + var nativeData = bender.tools.mockNativeDataTransfer(), + dataTransferFallback = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData )._.fallbackDataTransfer; + + assert.areEqual( '', dataTransferFallback._getFallbackTypeContent() ); + }, + + 'test getFallbackTypeData prioritize cache': function() { + var nativeData = bender.tools.mockNativeDataTransfer(), + dataTransferFallback = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData )._.fallbackDataTransfer; + + dataTransferFallback._cache[ 'cke/id' ] = 'cache value'; + CKEDITOR.plugins.clipboard.fallbackDataTransfer._customTypes.push( 'cke/id' ); + nativeData.setData( dataTransferFallback._customDataFallbackType, + dataTransferFallback._applyDataComment( 'html', { 'cke/id': 'native value' } ) ); + + objectAssert.areEqual( { + 'cke/id': 'cache value' + }, dataTransferFallback._getFallbackTypeData() ); + }, + + 'test getFallbackTypeData fallbacks to native data': function() { + var nativeData = bender.tools.mockNativeDataTransfer(), + dataTransferFallback = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData )._.fallbackDataTransfer; + + nativeData.setData( dataTransferFallback._customDataFallbackType, + dataTransferFallback._applyDataComment( 'html', { 'cke/id': 'native value' } ) ); + + objectAssert.areEqual( { + 'cke/id': 'native value' + }, dataTransferFallback._getFallbackTypeData() ); + }, + + 'test getFallbackTypeData for empty content in both cache and native data': function() { + var nativeData = bender.tools.mockNativeDataTransfer(), + dataTransferFallback = new CKEDITOR.plugins.clipboard.dataTransfer( nativeData )._.fallbackDataTransfer; + + objectAssert.areEqual( {}, dataTransferFallback._getFallbackTypeData() ); + }, + + assertDataTransferType: function( dataTransfer, type, value, customValue ) { + if ( CKEDITOR.env.ie && CKEDITOR.env.version >= 16 && customValue ) { + value = '' + value; + } + + var nativeDataTransfer = dataTransfer.$ || dataTransfer._nativeDataTransfer; + assert.areSame( value, nativeDataTransfer.getData( type ) ); + }, + + assertApplyDataComment: function( content, data, dataTransferFallback, expected ) { + assert.isInnerHtmlMatching( expected.replace( /[\n\r\t]*/g, '' ), dataTransferFallback._applyDataComment( content, data ) ); + } +} ); + +// Gets data with omitting the cache. +function getDataNoCache( dataTransfer, type ) { + return dataTransfer._.fallbackDataTransfer.getData( type ); +} diff --git a/tests/plugins/clipboard/manual/customtypes.html b/tests/plugins/clipboard/manual/customtypes.html new file mode 100644 index 00000000000..9783c2fb4d2 --- /dev/null +++ b/tests/plugins/clipboard/manual/customtypes.html @@ -0,0 +1,90 @@ +Copy content and paste content here.
+Type | +Value | +
---|
Foo Bar Baz
+Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:
+ +++ +One small step for [a] man, one giant leap for mankind.
+
Position | +Astronaut | +
---|---|
Commander | +Neil A. Armstrong | +
Command Module Pilot | +Michael Collins | +
Lunar Module Pilot | +Edwin "Buzz" E. Aldrin, Jr. | +
Aenean cursus egestas ipsum.diff --git a/tests/plugins/clipboard/manual/paste.html b/tests/plugins/clipboard/manual/paste.html index 373a3474970..cb4a4316332 100644 --- a/tests/plugins/clipboard/manual/paste.html +++ b/tests/plugins/clipboard/manual/paste.html @@ -196,7 +196,7 @@
x^x@
', bender.tools.selection.getWithHtml( editor ), { compareSelection: true, normalizeSelection: true }, 'Editor content' ); assert.areSame( pasteEventMock.$.clipboardData, CKEDITOR.plugins.clipboard.copyCutData.$, 'copyCutData should be initialized' ); @@ -1204,7 +1212,14 @@ editable.fire( 'copy', pasteEventMock ); - assert.areSame( 'bar', pasteEventMock.$.clipboardData.getData( 'text/html' ), 'HTML data' ); + // As Edge stores custom data in text/html it needs to be assert differently - we need to extract content part (#962). + if ( CKEDITOR.env.edge ) { + var dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( {}, editor ); + assert.areSame( 'bar', dataTransfer._.fallbackDataTransfer._extractDataComment( pasteEventMock.$.clipboardData.getData( 'text/html' ) ).content, 'HTML text' ); + } else { + assert.areSame( 'bar', pasteEventMock.$.clipboardData.getData( 'text/html' ), 'HTML text' ); + } + assert.areSame( 'bar', pasteEventMock.$.clipboardData.getData( 'Text' ), 'Plain text data' ); assert.isInnerHtmlMatching( 'x[bar]x@
', bender.tools.selection.getWithHtml( editor ), { compareSelection: true, normalizeSelection: true }, 'Editor content' ); assert.areSame( pasteEventMock.$.clipboardData, CKEDITOR.plugins.clipboard.copyCutData.$, 'copyCutData should be initialized' ); @@ -1288,17 +1303,23 @@ if ( !CKEDITOR.plugins.clipboard.isCustomCopyCutSupported ) assert.ignore(); - var editor = this.editor; + var editor = this.editor, + // As dataTransfer mock is used in `bender.tools.emulatePaste` we need to pass type which is acceptable in Edge + // as it does not support custom types (#962). + customType = CKEDITOR.env.edge ? 'application/xml' : 'cke/custom', + initialData = {}; + + initialData[ customType ] = 'foo'; this.on( 'paste', function( evt ) { resume( function() { assert.areSame( 'paste', evt.data.method, 'Paste method.' ); - assert.areSame( 'foo', evt.data.dataTransfer.getData( 'cke/custom' ), 'cke/custom data' ); + assert.areSame( 'foo', evt.data.dataTransfer.getData( customType ), 'cke/custom data' ); assert.areSame( '', evt.data.dataValue, 'dataValue' ); } ); } ); - bender.tools.emulatePaste( editor, '', { 'cke/custom': 'foo' } ); + bender.tools.emulatePaste( editor, '', initialData ); this.wait(); }, @@ -1362,6 +1383,23 @@ assert.isTrue( canClipboardApiBeTrusted( dataTransfer ), 'Clipboard API should be marked as trusted.' ); }, + // #468 + 'test canClipboardApiBeTrusted in Edge 16+': function() { + if ( !trustyEdge ) { + assert.ignore(); + } + + var canClipboardApiBeTrusted = CKEDITOR.plugins.clipboard.canClipboardApiBeTrusted, + nativeData = bender.tools.mockNativeDataTransfer(); + + nativeData.setData( 'text/html', 'foo' ); + + var evt = { data: { $: { clipboardData: nativeData } } }, + dataTransfer = CKEDITOR.plugins.clipboard.initPasteDataTransfer( evt ); + + assert.isTrue( canClipboardApiBeTrusted( dataTransfer ), 'Clipboard API should be marked as trusted.' ); + }, + 'test canClipboardApiBeTrusted in Android Chrome (no dataTransfer support)': function() { if ( !CKEDITOR.env.chrome ) { assert.ignore(); @@ -1425,7 +1463,7 @@ }, 'test canClipboardApiBeTrusted on other browser': function() { - if ( CKEDITOR.env.chrome || CKEDITOR.env.gecko || trustySafari ) { + if ( CKEDITOR.env.chrome || CKEDITOR.env.gecko || trustySafari || trustyEdge ) { assert.ignore(); } diff --git a/tests/plugins/pastefromword/parsestyles.html b/tests/plugins/pastefromword/parsestyles.html index d1a9eea6930..b44f42e5005 100644 --- a/tests/plugins/pastefromword/parsestyles.html +++ b/tests/plugins/pastefromword/parsestyles.html @@ -18,13 +18,13 @@ mso-style-qformat:yes; mso-style-parent:""; margin:0cm; - margin-bottom:.0001pt; + margin-bottom:.001pt; } => [ { "selector": "p.MsoNormal, li.MsoNormal, div.MsoNormal", "styles": { - "margin": "0cm 0cm 0.0001pt" + "margin": "0cm 0cm 0.001pt" } } ] @@ -39,7 +39,7 @@ mso-style-qformat:yes; mso-style-parent:""; margin:0cm; - margin-bottom:.0001pt; + margin-bottom:.001pt; } => [ { @@ -50,7 +50,7 @@ }, { "selector": "p.MsoNormal, li.MsoNormal, div.MsoNormal", "styles": { - "margin": "0cm 0cm 0.0001pt" + "margin": "0cm 0cm 0.001pt" } } ] diff --git a/tests/plugins/pastefromword/parsestyles.js b/tests/plugins/pastefromword/parsestyles.js index 8948419c363..f4d9f926916 100644 --- a/tests/plugins/pastefromword/parsestyles.js +++ b/tests/plugins/pastefromword/parsestyles.js @@ -12,6 +12,14 @@ bender.tools.testInputOut( name, function( styles, expected ) { var tested = CKEDITOR.plugins.pastefromword.styles.inliner.parse( styles ); + // In `CKEDITOR.plugins.pastefromword.styles.inliner.parse#createIsolatedStylesheet` + // function Edge camelcases the selectors so we need to lowercase it (#1042). + if ( CKEDITOR.plugins.clipboard.isCustomCopyCutSupported && CKEDITOR.env.edge ) { + CKEDITOR.tools.array.forEach( tested, function( item ) { + item.selector = item.selector.toLowerCase(); + } ); + } + assert.beautified.js( expected, JSON.stringify( tested ), name ); } ); } diff --git a/tests/plugins/tableselection/integrations/clipboard/pasteflow.js b/tests/plugins/tableselection/integrations/clipboard/pasteflow.js index 016293d1d2d..95414de5248 100644 --- a/tests/plugins/tableselection/integrations/clipboard/pasteflow.js +++ b/tests/plugins/tableselection/integrations/clipboard/pasteflow.js @@ -50,13 +50,14 @@ } ); } + // Tests breaks bender run in Edge 16+ when in reversed order (#1047). var tests = { - 'test paste flow (tabular content)': function( editor, bot ) { - testPasteFlow( bot, 'tabular-paste', '2cells1row' ); - }, - 'test paste flow (non-tabular content)': function( editor, bot ) { testPasteFlow( bot, 'nontabular-paste', 'paragraph' ); + }, + + 'test paste flow (tabular content)': function( editor, bot ) { + testPasteFlow( bot, 'tabular-paste', '2cells1row' ); } }; diff --git a/tests/plugins/widget/acf.js b/tests/plugins/widget/acf.js index b3aa592a97b..bb2e7c355f9 100644 --- a/tests/plugins/widget/acf.js +++ b/tests/plugins/widget/acf.js @@ -97,7 +97,15 @@ var editor = bot.editor; assert.areSame( 3, obj2Array( editor.widgets.instances ).length ); - assert.areSame( 'foobarbimbom
foobarbimbom
foo
xfoox
', function() { - var evt = { data: bender.tools.mockDropEvent() }, + var evt = { data: bender.tools.mockDropEvent(), name: 'dragstart' }, range = editor.createRange(), dropCalled = false, dropNotCancelled = false;