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 #176 from ckeditor/t/ckeditor5/746
Browse files Browse the repository at this point in the history
Feature: Introduced a `secureSourceElement()` utility that prevents from initialising more than one editor on the same DOM element. See ckeditor/ckeditor5#746.
  • Loading branch information
jodator committed Jul 23, 2019
2 parents cba2fd6 + b8656e4 commit 6a59058
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 1 deletion.
4 changes: 3 additions & 1 deletion src/editor/editorui.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ export default class EditorUI {
// It helps 3rd–party software (browser extensions, other libraries) access and recognize
// CKEditor 5 instances (editing roots) and use their API (there is no global editor
// instance registry).
domElement.ckeditorInstance = this.editor;
if ( !domElement.ckeditorInstance ) {
domElement.ckeditorInstance = this.editor;
}
}

/**
Expand Down
50 changes: 50 additions & 0 deletions src/editor/utils/securesourceelement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';

/**
* @module core/editor/utils/securesourceelement
*/

/**
* Marks the source element on which the editor was initialized. This prevents other editor instances from using this element.
*
* Running multiple editor instances on the same source element causes various issues and it is
* crucial this helper is called as soon as the source element is known to prevent collisions.
*
* @param {module:core/editor/editor~Editor} editor Editor instance.
*/
export default function secureSourceElement( editor ) {
const sourceElement = editor.sourceElement;

// If the editor was initialized without specifying an element, we don't need to secure anything.
if ( !sourceElement ) {
return;
}

if ( sourceElement.ckeditorInstance ) {
/**
* A DOM element used to create the editor (e.g.
* {@link module:editor-inline/inlineeditor~InlineEditor.create `InlineEditor.create()`})
* has already been used to create another editor instance. Make sure each editor is
* created with an unique DOM element.
*
* @error editor-source-element-already-used
* @param {HTMLElement} element DOM element that caused the collision.
*/
throw new CKEditorError(
'editor-source-element-already-used: ' +
'The DOM element cannot be used to create multiple editor instances.',
editor
);
}

sourceElement.ckeditorInstance = editor;

editor.once( 'destroy', () => {
delete sourceElement.ckeditorInstance;
} );
}
11 changes: 11 additions & 0 deletions tests/editor/editorui.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ describe( 'EditorUI', () => {

expect( element.ckeditorInstance ).to.equal( editor );
} );

it( 'does not override a reference to the editor instance in domElement#ckeditorInstance', () => {
const ui = new EditorUI( editor );
const element = document.createElement( 'div' );

element.ckeditorInstance = 'foo';

ui.setEditableElement( 'main', element );

expect( element.ckeditorInstance ).to.equal( 'foo' );
} );
} );

describe( 'getEditableElement()', () => {
Expand Down
67 changes: 67 additions & 0 deletions tests/editor/utils/securesourceelement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/* global document */

import secureSourceElement from '../../../src/editor/utils/securesourceelement';
import Editor from '../../../src/editor/editor';
import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';

describe( 'secureSourceElement()', () => {
let editor, sourceElement;

beforeEach( () => {
class CustomEditor extends Editor {}

sourceElement = document.createElement( 'div' );
editor = new CustomEditor();

editor.sourceElement = sourceElement;
editor.state = 'ready';
} );

afterEach( () => {
if ( editor ) {
return editor.destroy();
}
} );

it( 'does not throw if the editor was not initialized using the source element', () => {
delete editor.sourceElement;

expect( () => {
secureSourceElement( editor );
} ).to.not.throw();
} );

it( 'does not throw if the editor was initialized using the element for the first time', () => {
expect( () => {
secureSourceElement( editor );
} ).to.not.throw();
} );

it( 'sets the property after initializing the editor', () => {
secureSourceElement( editor );

expect( sourceElement.ckeditorInstance ).to.equal( editor );
} );

it( 'removes the property after destroying the editor', () => {
secureSourceElement( editor );

return editor.destroy()
.then( () => {
expect( sourceElement.ckeditorInstance ).to.be.undefined;
} );
} );

it( 'throws an error if the same element was used twice', () => {
sourceElement.ckeditorInstance = 'foo';

expectToThrowCKEditorError( () => {
secureSourceElement( editor );
}, /^editor-source-element-already-used/, editor );
} );
} );

0 comments on commit 6a59058

Please sign in to comment.