MDL-47133 editor_atto: Use strict validation of keyboard shortcuts

This commit is contained in:
Frederic Massart 2014-11-25 11:56:47 +08:00
parent ca0e301c7b
commit 04b12fc1b3
5 changed files with 154 additions and 19 deletions

View File

@ -0,0 +1,9 @@
This files describes API changes in the editor_atto code.
=== 2.9 ===
* When adding a shortcut to the button of a plugin, atto will add a layer of validation
to ensure that only the required keys are pressed. However, if you are using a custom
keyConfig object you must validate the shortcut yourself. This is particularly important
for non-English keyboard users. For more information read the documentation of
EditorPluginButtons::_addKeyboardListener() and MDL-47133.

View File

@ -783,7 +783,11 @@ EditorPluginButtons.prototype = {
* The keyConfig will take either an array of keyConfigurations, in
* which case _addKeyboardListener is called multiple times; an object
* containing an optional eventtype, optional container, and a set of
* keyCodes, or just a string containing the keyCodes.
* keyCodes, or just a string containing the keyCodes. When keyConfig is
* not an object, it is wrapped around a function that ensures that
* only the expected key modifiers were used. For instance, it checks
* that space+ctrl is not triggered when the user presses ctrl+shift+space.
* When using an object, the developer should check that manually.
*
* @method _addKeyboardListener
* @param {function} callback
@ -797,7 +801,9 @@ EditorPluginButtons.prototype = {
_addKeyboardListener: function(callback, keyConfig, buttonName) {
var eventtype = 'key',
container = CSS.EDITORWRAPPER,
keys;
keys,
handler,
modifier;
if (Y.Lang.isArray(keyConfig)) {
// If an Array was specified, call the add function for each element.
@ -818,19 +824,27 @@ EditorPluginButtons.prototype = {
// Must be specified.
keys = keyConfig.keyCodes;
handler = callback;
} else {
keys = this._getKeyEvent() + keyConfig + this._getDefaultMetaKey();
modifier = this._getDefaultMetaKey()
keys = this._getKeyEvent() + keyConfig + '+' + modifier;
if (typeof this._primaryKeyboardShortcut[buttonName] === 'undefined') {
this._primaryKeyboardShortcut[buttonName] = this._getDefaultMetaKeyDescription(keyConfig);
}
// Wrap the callback into a handler to check if it uses the specified modifiers, not more.
handler = Y.bind(function(modifiers, e) {
if (this._eventUsesExactKeyModifiers(modifiers, e)) {
callback.apply(this, [e]);
}
}, this, [modifier]);
}
this._buttonHandlers.push(
this.editor.delegate(
eventtype,
callback,
handler,
keys,
container,
this
@ -841,6 +855,34 @@ EditorPluginButtons.prototype = {
'debug', LOGNAME);
},
/**
* Checks if a key event was strictly defined for the modifiers passed.
*
* @method _eventUsesExactKeyModifiers
* @param {Array} modifiers List of key modifiers to check for (alt, ctrl, meta or shift).
* @param {EventFacade} e The event facade.
* @return {Boolean} True if the event was stricly using the modifiers specified.
*/
_eventUsesExactKeyModifiers: function(modifiers, e) {
var exactMatch = true,
hasKey;
if (e.type != 'key') {
return false;
}
hasKey = Y.Array.indexOf(modifiers, 'alt') > -1;
exactMatch = exactMatch && ((e.altKey && hasKey) || (!e.altKey && !hasKey));
hasKey = Y.Array.indexOf(modifiers, 'ctrl') > -1;
exactMatch = exactMatch && ((e.ctrlKey && hasKey) || (!e.ctrlKey && !hasKey));
hasKey = Y.Array.indexOf(modifiers, 'meta') > -1;
exactMatch = exactMatch && ((e.metaKey && hasKey) || (!e.metaKey && !hasKey));
hasKey = Y.Array.indexOf(modifiers, 'shift') > -1;
exactMatch = exactMatch && ((e.shiftKey && hasKey) || (!e.shiftKey && !hasKey));
return exactMatch;
},
/**
* Determine if this plugin is enabled, based upon the state of it's buttons.
*
@ -978,9 +1020,9 @@ EditorPluginButtons.prototype = {
*/
_getDefaultMetaKey: function() {
if (Y.UA.os === 'macintosh') {
return '+meta';
return 'meta';
} else {
return '+ctrl';
return 'ctrl';
}
},

File diff suppressed because one or more lines are too long

View File

@ -781,7 +781,11 @@ EditorPluginButtons.prototype = {
* The keyConfig will take either an array of keyConfigurations, in
* which case _addKeyboardListener is called multiple times; an object
* containing an optional eventtype, optional container, and a set of
* keyCodes, or just a string containing the keyCodes.
* keyCodes, or just a string containing the keyCodes. When keyConfig is
* not an object, it is wrapped around a function that ensures that
* only the expected key modifiers were used. For instance, it checks
* that space+ctrl is not triggered when the user presses ctrl+shift+space.
* When using an object, the developer should check that manually.
*
* @method _addKeyboardListener
* @param {function} callback
@ -795,7 +799,9 @@ EditorPluginButtons.prototype = {
_addKeyboardListener: function(callback, keyConfig, buttonName) {
var eventtype = 'key',
container = CSS.EDITORWRAPPER,
keys;
keys,
handler,
modifier;
if (Y.Lang.isArray(keyConfig)) {
// If an Array was specified, call the add function for each element.
@ -816,19 +822,27 @@ EditorPluginButtons.prototype = {
// Must be specified.
keys = keyConfig.keyCodes;
handler = callback;
} else {
keys = this._getKeyEvent() + keyConfig + this._getDefaultMetaKey();
modifier = this._getDefaultMetaKey()
keys = this._getKeyEvent() + keyConfig + '+' + modifier;
if (typeof this._primaryKeyboardShortcut[buttonName] === 'undefined') {
this._primaryKeyboardShortcut[buttonName] = this._getDefaultMetaKeyDescription(keyConfig);
}
// Wrap the callback into a handler to check if it uses the specified modifiers, not more.
handler = Y.bind(function(modifiers, e) {
if (this._eventUsesExactKeyModifiers(modifiers, e)) {
callback.apply(this, [e]);
}
}, this, [modifier]);
}
this._buttonHandlers.push(
this.editor.delegate(
eventtype,
callback,
handler,
keys,
container,
this
@ -837,6 +851,34 @@ EditorPluginButtons.prototype = {
},
/**
* Checks if a key event was strictly defined for the modifiers passed.
*
* @method _eventUsesExactKeyModifiers
* @param {Array} modifiers List of key modifiers to check for (alt, ctrl, meta or shift).
* @param {EventFacade} e The event facade.
* @return {Boolean} True if the event was stricly using the modifiers specified.
*/
_eventUsesExactKeyModifiers: function(modifiers, e) {
var exactMatch = true,
hasKey;
if (e.type != 'key') {
return false;
}
hasKey = Y.Array.indexOf(modifiers, 'alt') > -1;
exactMatch = exactMatch && ((e.altKey && hasKey) || (!e.altKey && !hasKey));
hasKey = Y.Array.indexOf(modifiers, 'ctrl') > -1;
exactMatch = exactMatch && ((e.ctrlKey && hasKey) || (!e.ctrlKey && !hasKey));
hasKey = Y.Array.indexOf(modifiers, 'meta') > -1;
exactMatch = exactMatch && ((e.metaKey && hasKey) || (!e.metaKey && !hasKey));
hasKey = Y.Array.indexOf(modifiers, 'shift') > -1;
exactMatch = exactMatch && ((e.shiftKey && hasKey) || (!e.shiftKey && !hasKey));
return exactMatch;
},
/**
* Determine if this plugin is enabled, based upon the state of it's buttons.
*
@ -974,9 +1016,9 @@ EditorPluginButtons.prototype = {
*/
_getDefaultMetaKey: function() {
if (Y.UA.os === 'macintosh') {
return '+meta';
return 'meta';
} else {
return '+ctrl';
return 'ctrl';
}
},

View File

@ -634,7 +634,11 @@ EditorPluginButtons.prototype = {
* The keyConfig will take either an array of keyConfigurations, in
* which case _addKeyboardListener is called multiple times; an object
* containing an optional eventtype, optional container, and a set of
* keyCodes, or just a string containing the keyCodes.
* keyCodes, or just a string containing the keyCodes. When keyConfig is
* not an object, it is wrapped around a function that ensures that
* only the expected key modifiers were used. For instance, it checks
* that space+ctrl is not triggered when the user presses ctrl+shift+space.
* When using an object, the developer should check that manually.
*
* @method _addKeyboardListener
* @param {function} callback
@ -648,7 +652,9 @@ EditorPluginButtons.prototype = {
_addKeyboardListener: function(callback, keyConfig, buttonName) {
var eventtype = 'key',
container = CSS.EDITORWRAPPER,
keys;
keys,
handler,
modifier;
if (Y.Lang.isArray(keyConfig)) {
// If an Array was specified, call the add function for each element.
@ -669,19 +675,27 @@ EditorPluginButtons.prototype = {
// Must be specified.
keys = keyConfig.keyCodes;
handler = callback;
} else {
keys = this._getKeyEvent() + keyConfig + this._getDefaultMetaKey();
modifier = this._getDefaultMetaKey()
keys = this._getKeyEvent() + keyConfig + '+' + modifier;
if (typeof this._primaryKeyboardShortcut[buttonName] === 'undefined') {
this._primaryKeyboardShortcut[buttonName] = this._getDefaultMetaKeyDescription(keyConfig);
}
// Wrap the callback into a handler to check if it uses the specified modifiers, not more.
handler = Y.bind(function(modifiers, e) {
if (this._eventUsesExactKeyModifiers(modifiers, e)) {
callback.apply(this, [e]);
}
}, this, [modifier]);
}
this._buttonHandlers.push(
this.editor.delegate(
eventtype,
callback,
handler,
keys,
container,
this
@ -692,6 +706,34 @@ EditorPluginButtons.prototype = {
'debug', LOGNAME);
},
/**
* Checks if a key event was strictly defined for the modifiers passed.
*
* @method _eventUsesExactKeyModifiers
* @param {Array} modifiers List of key modifiers to check for (alt, ctrl, meta or shift).
* @param {EventFacade} e The event facade.
* @return {Boolean} True if the event was stricly using the modifiers specified.
*/
_eventUsesExactKeyModifiers: function(modifiers, e) {
var exactMatch = true,
hasKey;
if (e.type != 'key') {
return false;
}
hasKey = Y.Array.indexOf(modifiers, 'alt') > -1;
exactMatch = exactMatch && ((e.altKey && hasKey) || (!e.altKey && !hasKey));
hasKey = Y.Array.indexOf(modifiers, 'ctrl') > -1;
exactMatch = exactMatch && ((e.ctrlKey && hasKey) || (!e.ctrlKey && !hasKey));
hasKey = Y.Array.indexOf(modifiers, 'meta') > -1;
exactMatch = exactMatch && ((e.metaKey && hasKey) || (!e.metaKey && !hasKey));
hasKey = Y.Array.indexOf(modifiers, 'shift') > -1;
exactMatch = exactMatch && ((e.shiftKey && hasKey) || (!e.shiftKey && !hasKey));
return exactMatch;
},
/**
* Determine if this plugin is enabled, based upon the state of it's buttons.
*
@ -829,9 +871,9 @@ EditorPluginButtons.prototype = {
*/
_getDefaultMetaKey: function() {
if (Y.UA.os === 'macintosh') {
return '+meta';
return 'meta';
} else {
return '+ctrl';
return 'ctrl';
}
},