Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Clipboard API in Edge #1153

Merged
merged 54 commits into from
Nov 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
52bf742
Switch on support for Clipboard API in Edge 15+.
Comandeer Jun 10, 2017
afc8689
Updated tools for clipboard unit tests.
Comandeer Jun 10, 2017
d3a69d0
Updated tests for dataTransfer.
Comandeer Jun 10, 2017
ef8a99a
Clipboard support from Edge 16.
f1ames Sep 21, 2017
4a83cdf
Initial clipboard API support for Edge >= 16.
f1ames Sep 22, 2017
d6ef5d4
Edge workaround.
f1ames Sep 26, 2017
0d400d1
Unit tests.
f1ames Sep 26, 2017
f8cfded
Set id after setting other MIME types.
f1ames Sep 26, 2017
d70c521
Edge version fix in tests.
f1ames Sep 26, 2017
165bb20
Manual test added.
f1ames Sep 26, 2017
7a183f5
Specs corrections.
f1ames Sep 26, 2017
1d7763c
Fixed failing tests.
f1ames Sep 27, 2017
d75edd2
Test fixes.
f1ames Sep 27, 2017
1816924
Fix for 'OpenClipboard Failed' error.
f1ames Sep 28, 2017
4e58de6
Set native dataTransfer id only on certain events.
f1ames Oct 2, 2017
66b5698
Tests: set native dataTransfer id only on certain events.
f1ames Oct 2, 2017
06e990b
Extracted all Edge logic to 'CKEDITOR.plugins.clipboard.fallbackDataT…
f1ames Oct 10, 2017
9e7ac34
Introduced 'setId' method.
f1ames Oct 10, 2017
7f23317
Docs fix.
f1ames Oct 10, 2017
80cfc31
Fallback data transfer improvements.
f1ames Oct 11, 2017
0f4bdcc
Fixed tests.
f1ames Oct 11, 2017
2693b92
Handle empty 'dataTransfer' in 'isRequired' method.
f1ames Oct 11, 2017
c6f5791
Fixed failing test.
f1ames Oct 11, 2017
85d2f0d
Fix for 'dataTransfer.items'.
f1ames Oct 11, 2017
2dfff53
Proper Edge workaround description.
f1ames Oct 12, 2017
a4e1d48
Refactoring, simplified manual test.
f1ames Oct 13, 2017
3c63e6e
Added fallbackDataTransfer unit tests.
f1ames Oct 13, 2017
0479df3
Troublesome test workaround.
f1ames Oct 13, 2017
5522c4a
Docs adjustments. Simplified 'isRequired' method.
f1ames Oct 19, 2017
fd6f80a
Use 'clearData' to remove test mime type.
f1ames Oct 19, 2017
2726b40
Renamed 'fallbackDataTransfer' to '_fallbackDataTransfer'.
f1ames Oct 20, 2017
1b3abfe
Refactoring.
f1ames Oct 20, 2017
77f9262
Refactor and rename 'setId' to 'storeId'.
f1ames Oct 20, 2017
cc193f8
Specs adjustments.
f1ames Oct 20, 2017
1d31a9e
Added manual test for custom data transfer types.
mlewand Oct 24, 2017
ad6c1d6
Simplified manual test.
mlewand Oct 24, 2017
4afa20a
Improved manual test.
mlewand Oct 24, 2017
3be5608
Shared cache added to 'fallbackDataTransfer'.
f1ames Oct 26, 2017
a0faa5b
Fix for 'getNative' param.
f1ames Oct 26, 2017
0457606
Docs corrections.
f1ames Oct 26, 2017
baefc57
Corrected input data in manual test.
mlewand Nov 6, 2017
69fe8aa
Cache simplified and refactored.
f1ames Nov 7, 2017
e890411
Tests: refactoring and adjusting to new cache structure.
f1ames Nov 7, 2017
d006adb
Tests: corrected manual test not to leak undefined class.
mlewand Nov 8, 2017
4247839
Tests: use CKEditor dataTransfer API for getting raw HTML rather than…
mlewand Nov 8, 2017
b622622
Review fixes.
f1ames Nov 8, 2017
9a6a648
Tests: ignore datatransfer fallback manual test for browsers that do …
mlewand Nov 10, 2017
a4224bd
Tests: reverted failing tests workaround.
f1ames Nov 13, 2017
ed3b639
Fix nulling zero value in 'dataTransfer.setData' which breaks widgets…
f1ames Nov 14, 2017
ebc1380
Fix pastebin container in Edge.
f1ames Nov 15, 2017
0bebad8
Tests: manual test case.
f1ames Nov 15, 2017
cc2b48e
Edge 16+ - always use pastebin with 'div' element.
f1ames Nov 17, 2017
c109860
Tests: manual test improved.
f1ames Nov 17, 2017
fed646e
Added comment referencing more general issue.
f1ames Nov 20, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
405 changes: 372 additions & 33 deletions plugins/clipboard/plugin.js

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions plugins/widget/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -3082,17 +3082,21 @@

function copySingleWidget( widget, isCut ) {
var editor = widget.editor,
doc = editor.document;
doc = editor.document,
isEdge16 = CKEDITOR.env.edge && CKEDITOR.env.version >= 16;

// We're still handling previous copy/cut.
// When keystroke is used to copy/cut this will also prevent
// conflict with copySingleWidget called again for native copy/cut event.
if ( doc.getById( 'cke_copybin' ) )
return;

// [IE] Use span for copybin and its container to avoid bug with expanding editable height by
// absolutely positioned element.
var copybinName = ( editor.blockless || CKEDITOR.env.ie ) ? 'span' : 'div',
// [IE] Use span for copybin and its container to avoid bug with expanding
// editable height by absolutely positioned element.
// For Edge 16+ always use div as span causes scrolling to the end of the document
// on widget cut (also for blockless editor) (#1160).
// Edge 16+ workaround could be safetly removed after #1169 is fixed.
var copybinName = ( ( editor.blockless || CKEDITOR.env.ie ) && !isEdge16 ) ? 'span' : 'div',
copybin = doc.createElement( copybinName ),
copybinContainer = doc.createElement( copybinName ),
// IE8 always jumps to the end of document.
Expand Down
35 changes: 29 additions & 6 deletions tests/_benderjs/ckeditor/static/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -821,13 +821,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 @@ -839,13 +852,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 @@ -889,7 +912,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( {

// https://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( {

// https://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' );
}
} );
Loading