Skip to content

Commit

Permalink
Merge pull request #968 from ckeditor/t/468-962
Browse files Browse the repository at this point in the history
Workaround for Edge not supporting custom MIME types in `dataTransfer.setData`
  • Loading branch information
mlewand authored Nov 10, 2017
2 parents a0ba16d + 270fbda commit 49281a9
Show file tree
Hide file tree
Showing 16 changed files with 1,418 additions and 84 deletions.
405 changes: 372 additions & 33 deletions plugins/clipboard/plugin.js

Large diffs are not rendered by default.

35 changes: 29 additions & 6 deletions tests/_benderjs/ckeditor/static/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 );
}
}
};
},
Expand Down Expand Up @@ -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
Expand Down
187 changes: 161 additions & 26 deletions tests/plugins/clipboard/datatransfer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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( '<p>x[x<b>foo</b>x]x</p>' );

Expand Down Expand Up @@ -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', 'x<b>foo</b>x' );
Expand All @@ -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' );
Expand All @@ -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( '<p>x[x<b>foo</b>x]x</p>' );

Expand Down Expand Up @@ -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 = '<html>' +
'<head>' +
'<meta charset="UTF-8">' +
'<meta name="foo" content=bar>' +
'<STYLE>h1 { color: red; }</style>' +
'</head>' +
'<BODY>' +
'<!--StartFragment--><p>Foo</p>' +
'<p>Bar</p><!--EndFragment-->' +
'</body>' +
'</html>',
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' );
Expand All @@ -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' ) );
Expand Down Expand Up @@ -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' };
Expand Down Expand Up @@ -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' };
Expand Down Expand Up @@ -688,7 +726,6 @@ bender.test( {

nativeData.files.push( 'foo' );

// debugger;
dataTransfer.cacheData();

assert.areSame( 1, dataTransfer.getFilesCount() );
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -950,5 +987,103 @@ bender.test( {
text: isCustomDataTypesSupported ? 'xfoox' : '',
html: 'x<b>foo</b>x' },
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' );
}
} );
33 changes: 33 additions & 0 deletions tests/plugins/clipboard/fallbackdatatransfer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<div id="case1">
<!--cke-data:%7B%22test%22%3A1%7D-->
<h1>Header1</h1>
<p>Test1</p>
</div>

<div id="case2">
<!--cke-data:%7B%22test%22%3A1%2C%22comment%22%3A%22%3C!--%20comment%20--%3E%22%7D-->
<h1>Header1</h1>
<p>Test1</p>
</div>

<div id="case3">
<!--cke-data:%7B%22test%22%3A1%7D-->
<!-- Start Comment -->
<h1>Header1</h1>
<p>Test1</p>
</div>

<div id="case4">
<!--cke-data:%7B%22test%22%3A123%7D-->
<h1>Header1</h1>
<p>Test1</p>
<!-- End Comment -->
</div>

<div id="empty-content">
<!--cke-data:%7B%22test%22%3A1%7D-->
</div>

<div id="empty-data">
<p>foobar</p>
</div>
Loading

0 comments on commit 49281a9

Please sign in to comment.