-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Workaround for Edge not supporting custom MIME types in dataTransfer.setData
#968
Changes from 13 commits
bf67ea6
9d5d97f
b039172
36c4da1
f39e02d
280d3c0
cf26c37
293fcc6
f8c8e02
2427a6f
76a1bb9
b431eca
704a11f
2347c18
95078d2
ea41a52
a5dba7f
4eafeee
596f2b2
d6e8fcb
230e7ca
0404325
5d3abe1
514f93c
cac60ad
6223ae2
31a1a53
89e6830
59ab5cd
10b8bea
b3b776b
51c4045
0a2ed5d
4b77734
767f52c
28c4601
5e52ce3
e359599
9b93460
96bf403
0aac1fe
a94c5bd
9c0a25e
3c621f4
3375736
e61ac60
270fbda
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -187,6 +187,7 @@ | |
} | ||
|
||
editor.on( 'paste', function( evt ) { | ||
|
||
// Init `dataTransfer` if `paste` event was fired without it, so it will be always available. | ||
if ( !evt.data.dataTransfer ) { | ||
evt.data.dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer(); | ||
|
@@ -558,7 +559,6 @@ | |
// But don't you know any way to distinguish first two cases from last two? | ||
// Only one - special flag set in CTRL+V handler and exec method of 'paste' | ||
// command. And that's what we did using preventPasteEventNow(). | ||
|
||
pasteDataFromClipboard( evt ); | ||
} ); | ||
|
||
|
@@ -662,7 +662,6 @@ | |
this.type == 'cut' && fixCut(); | ||
|
||
var success = tryToCutCopy( this.type ); | ||
|
||
if ( !success ) { | ||
// Show cutError or copyError. | ||
editor.showNotification( editor.lang.clipboard[ this.type + 'Error' ] ); // jshint ignore:line | ||
|
@@ -1515,7 +1514,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 +1523,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 +1582,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; | ||
|
@@ -2079,7 +2085,7 @@ | |
*/ | ||
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 ); | ||
|
@@ -2132,6 +2138,8 @@ | |
this.$ = nativeDataTransfer; | ||
} | ||
|
||
this.customDataFallbackType = 'text/html'; | ||
|
||
this._ = { | ||
metaRegExp: /^<meta.*?>/i, | ||
bodyRegExp: /<body(?:[\s\S]*?)>([\s\S]*)<\/body>/i, | ||
|
@@ -2169,16 +2177,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; | ||
|
||
|
@@ -2191,6 +2189,13 @@ | |
} | ||
} | ||
|
||
// Set id as a last thing to not force rereading/rewriting custom data comment. | ||
// 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. | ||
this._setCustomData( clipboardIdDataType, this.id, CKEDITOR.env.ie && CKEDITOR.env.version >= 16 ); | ||
} | ||
|
||
/** | ||
* Data transfer ID used to bind all dataTransfer | ||
* objects based on the same event (e.g. in drag and drop events). | ||
|
@@ -2220,6 +2225,14 @@ | |
* @private | ||
* @property {Object} _ | ||
*/ | ||
|
||
/** | ||
* The MIME type which is used to store custom data (in special `<!-- cke-data: encoded JSON -->` comment) | ||
* when browser does not allow to use custom MIME types in `dataTransfer.setData`. | ||
* | ||
* @readonly | ||
* @property {String} customDataFallbackType | ||
*/ | ||
}; | ||
|
||
/** | ||
|
@@ -2288,6 +2301,7 @@ | |
type = this._.normalizeType( type ); | ||
|
||
var data = this._.data[ type ], | ||
content, | ||
result; | ||
|
||
if ( isEmpty( data ) ) { | ||
|
@@ -2296,6 +2310,21 @@ | |
} catch ( e ) {} | ||
} | ||
|
||
// If we are getting the same type which may store custom data we need to extract it (removing custom data comment). | ||
// Or if we are getting different type and it is empty we need to check inside data comment if it is stored there. | ||
if ( !isEmpty( data ) && type === this.customDataFallbackType ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With this construction every modern browser will take a performance hit when calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
content = this._extractDataComment( data ); | ||
data = content.content; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need for this empty line here. |
||
} else if ( isEmpty( data ) ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couldn't we do this part in this catch statement https://github.com/ckeditor/ckeditor-dev/blob/ea41a52/plugins/clipboard/plugin.js#L2326 ? AFAIR Edge throws an exception when calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The point is to execute this (possibly intense) operation as little as possible. This is a clipboard, so these kind of optimizations are important. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, Edge only throws error on |
||
try { | ||
content = this._extractDataComment( this.$.getData( this.customDataFallbackType ) ); | ||
if ( content.data && content.data[ type ] ) { | ||
data = content.data[ type ]; | ||
} | ||
} catch ( e ) {} | ||
} | ||
|
||
if ( isEmpty( data ) ) { | ||
data = ''; | ||
} | ||
|
@@ -2353,9 +2382,22 @@ | |
this.id = value; | ||
} | ||
|
||
try { | ||
this.$.setData( type, value ); | ||
} catch ( e ) {} | ||
// Extract custom data if used type is the same one which is used for storing it. | ||
// Then new value is added to a custom data so it will not be overwritten entirely. | ||
if ( type === this.customDataFallbackType ) { | ||
var content = null; | ||
try { | ||
content = this._extractDataComment( this.$.getData( this.customDataFallbackType ) ); | ||
} catch ( e ) {} | ||
|
||
if ( content && content.data ) { | ||
value = this._applyDataComment( value, content.data ); | ||
} | ||
this._setCustomData( type, value ); | ||
|
||
} else { | ||
this._setCustomData( type, value, CKEDITOR.env.ie && CKEDITOR.env.version >= 16 ); | ||
} | ||
}, | ||
|
||
/** | ||
|
@@ -2524,7 +2566,9 @@ | |
_getImageFromClipboard: function() { | ||
var file; | ||
|
||
if ( this.$ && this.$.items && this.$.items[ 0 ] ) { | ||
// The dataTransfer.items is not supported in IE/Edge. This function is used as a backup always after | ||
// dataTransfer.files is checked so there is no need for implementing more logic than ignoring IE/Edge (#468). | ||
if ( !CKEDITOR.env.ie && this.$ && this.$.items && this.$.items[ 0 ] ) { | ||
try { | ||
file = this.$.items[ 0 ].getAsFile(); | ||
// Duck typing | ||
|
@@ -2537,6 +2581,89 @@ | |
} | ||
|
||
return undefined; | ||
}, | ||
|
||
/** | ||
* Additional layer over `dataTransfer.setData` method. If used with `useFallback = true` in case of native `setData` | ||
* throwing an error it will try to place passed value in | ||
* {@link CKEDITOR.plugins.clipboard.dataTransfer#customDataFallbackType} type using special comment format: | ||
* | ||
* <!--cke-data:{ type: value }--> | ||
* | ||
* It is important to keep in mind that `{ type: value }` object is stringified (using `JSON.stringify`) | ||
* and encoded (using `encodeURIComponent`). | ||
* | ||
* @private | ||
* @param {String} type | ||
* @param {String} value | ||
* @param {Boolean} useFallback | ||
*/ | ||
_setCustomData: function( type, value, useFallback ) { | ||
try { | ||
this.$.setData( type, value ); | ||
|
||
} catch ( e ) { | ||
if ( useFallback ) { | ||
// Still in some situations some browsers may throw errors even when using predefined MIME types. | ||
try { | ||
// We need to get the data first to append `type` and not to overwrite whole custom data. | ||
var content = this._extractDataComment( this.$.getData( this.customDataFallbackType ) ); | ||
|
||
if ( !content.data ) { | ||
content.data = {}; | ||
} | ||
content.data[ type ] = value; | ||
|
||
this.$.setData( this.customDataFallbackType, this._applyDataComment( content.content, content.data ) ); | ||
} catch ( e ) {} | ||
} | ||
} | ||
}, | ||
|
||
/** | ||
* Extracts `cke-data` comment from the given content. Returns an object containing extracted data as `data` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It's better to inline it in a corresponding There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
* and content (without `cke-data` comment) as `content`. | ||
* | ||
* @private | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing introduction version number. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As it will be moved to a new class which as a whole has |
||
* @param {String} content | ||
* @returns {Object} | ||
* @returns {Object} return.data | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be a |
||
* @returns {String} return.content | ||
*/ | ||
_extractDataComment: function( content ) { | ||
var result = { | ||
data: null, | ||
content: content || '' | ||
}; | ||
|
||
// At least 17 characters length: <!--cke-data:-->. | ||
if ( content && content.length > 16 ) { | ||
var matcher = /<!--cke-data:(.*?)-->/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 = '<!--cke-data:' + encodeURIComponent( JSON.stringify( data ) ) + '-->'; | ||
} | ||
return customData + ( content && content.length ? content : '' ); | ||
} | ||
}; | ||
} )(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -816,13 +816,21 @@ | |
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; | ||
} | ||
|
||
if ( CKEDITOR.env.ie && CKEDITOR.env.version >= 16 && | ||
CKEDITOR.tools.indexOf( [ 'Text', 'URL', 'text/plain', 'text/html', 'application/xml' ], type ) === -1 ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A short comment explanation, would be useful here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, I believe it's valid only for Edge, isn't it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
throw 'Element not found.'; | ||
} | ||
|
||
if ( type == 'text/plain' || type == 'Text' ) { | ||
this._data[ 'text/plain' ] = data; | ||
|
@@ -834,11 +842,13 @@ | |
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 ]; | ||
} | ||
|
@@ -884,7 +894,7 @@ | |
return { | ||
$: { | ||
ctrlKey: true, | ||
clipboardData: CKEDITOR.env.ie ? undefined : dataTransfer | ||
clipboardData: ( CKEDITOR.env.ie && CKEDITOR.env.version < 16 ) ? undefined : dataTransfer | ||
}, | ||
preventDefault: function() { | ||
// noop | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -482,7 +482,10 @@ bender.test( { | |
// Emulate native clipboard. | ||
nativeData = bender.tools.mockNativeDataTransfer(); | ||
|
||
if ( isCustomDataTypesSupported ) { | ||
// This test uses native (mocked) `setData` which does not applies fallback | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find this comment is vague, first "native (mocked)" is a no go - is it native or a mock? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
// for Edge >= 16 (because it skips our 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 +504,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,7 +556,8 @@ bender.test( { | |
|
||
// http://dev.ckeditor.com/ticket/12961 | ||
'test file in items': function() { | ||
if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) { | ||
// DataTransfer.items is not supported in IE/EDGE so there is no reason to test it. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Important: actually AFAIR it works for drag events for IE11 (presumably also for Edge) so there is a good reason to keep that enabled. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
if ( CKEDITOR.env.ie ) { | ||
assert.ignore(); | ||
} | ||
|
||
|
@@ -642,7 +646,8 @@ bender.test( { | |
|
||
// http://dev.ckeditor.com/ticket/12961 | ||
'test file in items with cache': function() { | ||
if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) { | ||
// DataTransfer.items is not supported in IE/EDGE so there is no reason to test it. | ||
if ( CKEDITOR.env.ie ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
assert.ignore(); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should mark it as a private property. This is a hax that others should not be concerned about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With that it's name should be prefixed with an underscore.