Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #289 from ckeditor/t/ckeditor5-watchdog/1
Browse files Browse the repository at this point in the history
Other: Added context as second required argument to the `CKEditorError`'s constructor, changed `isCKEditorError()` method to `is()`. Introduced the `areConnectedThroughProperties()` utility Part of the ckeditor/ckeditor5-watchdog#1.
  • Loading branch information
Piotr Jasiun authored Jul 2, 2019
2 parents 591f641 + d182bdb commit bacc764
Show file tree
Hide file tree
Showing 14 changed files with 633 additions and 243 deletions.
92 changes: 92 additions & 0 deletions src/areconnectedthroughproperties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module utils/arestructuresconnected
*/

/* globals EventTarget, Event */

/**
* Traverses both structures to find out whether there is a reference that is shared between both structures.
*
* @param {Object|Array} obj1
* @param {Object|Array} obj2
*/
export default function areConnectedThroughProperties( obj1, obj2 ) {
if ( obj1 === obj2 && isObject( obj1 ) ) {
return true;
}

const subNodes1 = getSubNodes( obj1 );
const subNodes2 = getSubNodes( obj2 );

for ( const node of subNodes1 ) {
if ( subNodes2.has( node ) ) {
return true;
}
}

return false;
}

// Traverses JS structure and stores all sub-nodes, including the head node.
// It walks into each iterable structures with the `try catch` block to omit errors that might be thrown during
// tree walking. All primitives, functions and built-ins are skipped.
function getSubNodes( head ) {
const nodes = [ head ];

// Nodes are stored to prevent infinite looping.
const subNodes = new Set();

while ( nodes.length > 0 ) {
const node = nodes.shift();

if ( subNodes.has( node ) || shouldNodeBeSkipped( node ) ) {
continue;
}

subNodes.add( node );

// Handle arrays, maps, sets, custom collections that implements `[ Symbol.iterator ]()`, etc.
if ( node[ Symbol.iterator ] ) {
// The custom editor iterators might cause some problems if the editor is crashed.
try {
nodes.push( ...node );
} catch ( err ) {
// eslint-disable-line no-empty
}
} else {
nodes.push( ...Object.values( node ) );
}
}

return subNodes;
}

function shouldNodeBeSkipped( node ) {
const type = Object.prototype.toString.call( node );

return (
type === '[object Number]' ||
type === '[object Boolean]' ||
type === '[object String]' ||
type === '[object Symbol]' ||
type === '[object Function]' ||
type === '[object Date]' ||
type === '[object RegExp]' ||

node === undefined ||
node === null ||

// Skip native DOM objects, e.g. Window, nodes, events, etc.
node instanceof EventTarget ||
node instanceof Event
);
}

function isObject( structure ) {
return typeof structure === 'object' && structure !== null;
}
27 changes: 18 additions & 9 deletions src/ckeditorerror.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,16 @@ export default class CKEditorError extends Error {
* @param {String} message The error message in an `error-name: Error message.` format.
* During the minification process the "Error message" part will be removed to limit the code size
* and a link to this error documentation will be added to the `message`.
* @param {Object|null} context A context of the error by which the {@link module:watchdog/watchdog~Watchdog watchdog}
* is able to determine which editor crashed. It should be an editor instance or a property connected to it. It can be also
* a `null` value if the editor should not be restarted in case of the error (e.g. during the editor initialization).
* The error context should be checked using the `areConnectedThroughProperties( editor, context )` utility
* to check if the object works as the context.
* @param {Object} [data] Additional data describing the error. A stringified version of this object
* will be appended to the error message, so the data are quickly visible in the console. The original
* data object will also be later available under the {@link #data} property.
*/
constructor( message, data ) {
constructor( message, context, data ) {
message = attachLinkToDocumentation( message );

if ( data ) {
Expand All @@ -46,26 +51,30 @@ export default class CKEditorError extends Error {
super( message );

/**
* @member {String}
* @type {String}
*/
this.name = 'CKEditorError';

/**
* A context of the error by which the Watchdog is able to determine which editor crashed.
*
* @type {Object|null}
*/
this.context = context;

/**
* The additional error data passed to the constructor. Undefined if none was passed.
*
* @member {Object|undefined}
* @type {Object|undefined}
*/
this.data = data;
}

/**
* Checks if error is an instance of CKEditorError class.
*
* @param {Object} error Object to check.
* @returns {Boolean}
* Checks if the error is of the `CKEditorError` type.
*/
static isCKEditorError( error ) {
return error instanceof CKEditorError;
is( type ) {
return type === 'CKEditorError';
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export default class Collection {
*
* @error collection-add-invalid-id
*/
throw new CKEditorError( 'collection-add-invalid-id' );
throw new CKEditorError( 'collection-add-invalid-id', this );
}

if ( this.get( itemId ) ) {
Expand All @@ -157,7 +157,7 @@ export default class Collection {
*
* @error collection-add-item-already-exists
*/
throw new CKEditorError( 'collection-add-item-already-exists' );
throw new CKEditorError( 'collection-add-item-already-exists', this );
}
} else {
item[ idProperty ] = itemId = uid();
Expand All @@ -172,7 +172,7 @@ export default class Collection {
*
* @error collection-add-item-bad-index
*/
throw new CKEditorError( 'collection-add-item-invalid-index' );
throw new CKEditorError( 'collection-add-item-invalid-index', this );
}

this._items.splice( index, 0, item );
Expand Down Expand Up @@ -203,7 +203,7 @@ export default class Collection {
*
* @error collection-get-invalid-arg
*/
throw new CKEditorError( 'collection-get-invalid-arg: Index or id must be given.' );
throw new CKEditorError( 'collection-get-invalid-arg: Index or id must be given.', this );
}

return item || null;
Expand Down Expand Up @@ -286,7 +286,7 @@ export default class Collection {
*
* @error collection-remove-404
*/
throw new CKEditorError( 'collection-remove-404: Item not found.' );
throw new CKEditorError( 'collection-remove-404: Item not found.', this );
}

this._items.splice( index, 1 );
Expand Down Expand Up @@ -459,7 +459,7 @@ export default class Collection {
*
* @error collection-bind-to-rebind
*/
throw new CKEditorError( 'collection-bind-to-rebind: The collection cannot be bound more than once.' );
throw new CKEditorError( 'collection-bind-to-rebind: The collection cannot be bound more than once.', this );
}

this._bindToCollection = externalCollection;
Expand Down
2 changes: 1 addition & 1 deletion src/focustracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default class FocusTracker {
*/
add( element ) {
if ( this._elements.has( element ) ) {
throw new CKEditorError( 'focusTracker-add-element-already-exist' );
throw new CKEditorError( 'focusTracker-add-element-already-exist', this );
}

this.listenTo( element, 'focus', () => this._focus( element ), { useCapture: true } );
Expand Down
5 changes: 4 additions & 1 deletion src/keyboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ export function getCode( key ) {
* @errror keyboard-unknown-key
* @param {String} key
*/
throw new CKEditorError( 'keyboard-unknown-key: Unknown key name.', { key } );
throw new CKEditorError(
'keyboard-unknown-key: Unknown key name.',
null, { key }
);
}
} else {
keyCode = key.keyCode +
Expand Down
29 changes: 18 additions & 11 deletions src/observablemixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const ObservableMixin = {
*
* @error observable-set-cannot-override
*/
throw new CKEditorError( 'observable-set-cannot-override: Cannot override an existing property.' );
throw new CKEditorError( 'observable-set-cannot-override: Cannot override an existing property.', this );
}

Object.defineProperty( this, name, {
Expand Down Expand Up @@ -107,7 +107,7 @@ const ObservableMixin = {
*
* @error observable-bind-wrong-properties
*/
throw new CKEditorError( 'observable-bind-wrong-properties: All properties must be strings.' );
throw new CKEditorError( 'observable-bind-wrong-properties: All properties must be strings.', this );
}

if ( ( new Set( bindProperties ) ).size !== bindProperties.length ) {
Expand All @@ -116,7 +116,7 @@ const ObservableMixin = {
*
* @error observable-bind-duplicate-properties
*/
throw new CKEditorError( 'observable-bind-duplicate-properties: Properties must be unique.' );
throw new CKEditorError( 'observable-bind-duplicate-properties: Properties must be unique.', this );
}

initObservable( this );
Expand All @@ -130,7 +130,7 @@ const ObservableMixin = {
*
* @error observable-bind-rebind
*/
throw new CKEditorError( 'observable-bind-rebind: Cannot bind the same property more that once.' );
throw new CKEditorError( 'observable-bind-rebind: Cannot bind the same property more that once.', this );
}
} );

Expand Down Expand Up @@ -186,7 +186,7 @@ const ObservableMixin = {
*
* @error observable-unbind-wrong-properties
*/
throw new CKEditorError( 'observable-unbind-wrong-properties: Properties must be strings.' );
throw new CKEditorError( 'observable-unbind-wrong-properties: Properties must be strings.', this );
}

unbindProperties.forEach( propertyName => {
Expand Down Expand Up @@ -246,6 +246,7 @@ const ObservableMixin = {
*/
throw new CKEditorError(
'observablemixin-cannot-decorate-undefined: Cannot decorate an undefined method.',
this,
{ object: this, methodName }
);
}
Expand Down Expand Up @@ -380,7 +381,10 @@ function bindTo( ...args ) {
*
* @error observable-bind-no-callback
*/
throw new CKEditorError( 'observable-bind-to-no-callback: Binding multiple observables only possible with callback.' );
throw new CKEditorError(
'observable-bind-to-no-callback: Binding multiple observables only possible with callback.',
this
);
}

// Eliminate A.bind( 'x', 'y' ).to( B, callback )
Expand All @@ -390,7 +394,10 @@ function bindTo( ...args ) {
*
* @error observable-bind-to-extra-callback
*/
throw new CKEditorError( 'observable-bind-to-extra-callback: Cannot bind multiple properties and use a callback in one binding.' );
throw new CKEditorError(
'observable-bind-to-extra-callback: Cannot bind multiple properties and use a callback in one binding.',
this
);
}

parsedArgs.to.forEach( to => {
Expand All @@ -401,7 +408,7 @@ function bindTo( ...args ) {
*
* @error observable-bind-to-properties-length
*/
throw new CKEditorError( 'observable-bind-to-properties-length: The number of properties must match.' );
throw new CKEditorError( 'observable-bind-to-properties-length: The number of properties must match.', this );
}

// When no to.properties specified, observing source properties instead i.e.
Expand Down Expand Up @@ -442,7 +449,7 @@ function bindToMany( observables, attribute, callback ) {
*
* @error observable-bind-to-many-not-one-binding
*/
throw new CKEditorError( 'observable-bind-to-many-not-one-binding: Cannot bind multiple properties with toMany().' );
throw new CKEditorError( 'observable-bind-to-many-not-one-binding: Cannot bind multiple properties with toMany().', this );
}

this.to(
Expand Down Expand Up @@ -501,7 +508,7 @@ function parseBindToArgs( ...args ) {
*
* @error observable-bind-to-parse-error
*/
throw new CKEditorError( 'observable-bind-to-parse-error: Invalid argument syntax in `to()`.' );
throw new CKEditorError( 'observable-bind-to-parse-error: Invalid argument syntax in `to()`.', null );
}

const parsed = { to: [] };
Expand All @@ -518,7 +525,7 @@ function parseBindToArgs( ...args ) {
lastObservable = { observable: a, properties: [] };
parsed.to.push( lastObservable );
} else {
throw new CKEditorError( 'observable-bind-to-parse-error: Invalid argument syntax in `to()`.' );
throw new CKEditorError( 'observable-bind-to-parse-error: Invalid argument syntax in `to()`.', null );
}
} );

Expand Down
Loading

0 comments on commit bacc764

Please sign in to comment.