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

Commit 6a59058

Browse files
authored
Merge pull request #176 from ckeditor/t/ckeditor5/746
Feature: Introduced a `secureSourceElement()` utility that prevents from initialising more than one editor on the same DOM element. See ckeditor/ckeditor5#746.
2 parents cba2fd6 + b8656e4 commit 6a59058

File tree

4 files changed

+131
-1
lines changed

4 files changed

+131
-1
lines changed

src/editor/editorui.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ export default class EditorUI {
124124
// It helps 3rd–party software (browser extensions, other libraries) access and recognize
125125
// CKEditor 5 instances (editing roots) and use their API (there is no global editor
126126
// instance registry).
127-
domElement.ckeditorInstance = this.editor;
127+
if ( !domElement.ckeditorInstance ) {
128+
domElement.ckeditorInstance = this.editor;
129+
}
128130
}
129131

130132
/**
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4+
*/
5+
6+
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
7+
8+
/**
9+
* @module core/editor/utils/securesourceelement
10+
*/
11+
12+
/**
13+
* Marks the source element on which the editor was initialized. This prevents other editor instances from using this element.
14+
*
15+
* Running multiple editor instances on the same source element causes various issues and it is
16+
* crucial this helper is called as soon as the source element is known to prevent collisions.
17+
*
18+
* @param {module:core/editor/editor~Editor} editor Editor instance.
19+
*/
20+
export default function secureSourceElement( editor ) {
21+
const sourceElement = editor.sourceElement;
22+
23+
// If the editor was initialized without specifying an element, we don't need to secure anything.
24+
if ( !sourceElement ) {
25+
return;
26+
}
27+
28+
if ( sourceElement.ckeditorInstance ) {
29+
/**
30+
* A DOM element used to create the editor (e.g.
31+
* {@link module:editor-inline/inlineeditor~InlineEditor.create `InlineEditor.create()`})
32+
* has already been used to create another editor instance. Make sure each editor is
33+
* created with an unique DOM element.
34+
*
35+
* @error editor-source-element-already-used
36+
* @param {HTMLElement} element DOM element that caused the collision.
37+
*/
38+
throw new CKEditorError(
39+
'editor-source-element-already-used: ' +
40+
'The DOM element cannot be used to create multiple editor instances.',
41+
editor
42+
);
43+
}
44+
45+
sourceElement.ckeditorInstance = editor;
46+
47+
editor.once( 'destroy', () => {
48+
delete sourceElement.ckeditorInstance;
49+
} );
50+
}

tests/editor/editorui.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,17 @@ describe( 'EditorUI', () => {
130130

131131
expect( element.ckeditorInstance ).to.equal( editor );
132132
} );
133+
134+
it( 'does not override a reference to the editor instance in domElement#ckeditorInstance', () => {
135+
const ui = new EditorUI( editor );
136+
const element = document.createElement( 'div' );
137+
138+
element.ckeditorInstance = 'foo';
139+
140+
ui.setEditableElement( 'main', element );
141+
142+
expect( element.ckeditorInstance ).to.equal( 'foo' );
143+
} );
133144
} );
134145

135146
describe( 'getEditableElement()', () => {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4+
*/
5+
6+
/* global document */
7+
8+
import secureSourceElement from '../../../src/editor/utils/securesourceelement';
9+
import Editor from '../../../src/editor/editor';
10+
import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
11+
12+
describe( 'secureSourceElement()', () => {
13+
let editor, sourceElement;
14+
15+
beforeEach( () => {
16+
class CustomEditor extends Editor {}
17+
18+
sourceElement = document.createElement( 'div' );
19+
editor = new CustomEditor();
20+
21+
editor.sourceElement = sourceElement;
22+
editor.state = 'ready';
23+
} );
24+
25+
afterEach( () => {
26+
if ( editor ) {
27+
return editor.destroy();
28+
}
29+
} );
30+
31+
it( 'does not throw if the editor was not initialized using the source element', () => {
32+
delete editor.sourceElement;
33+
34+
expect( () => {
35+
secureSourceElement( editor );
36+
} ).to.not.throw();
37+
} );
38+
39+
it( 'does not throw if the editor was initialized using the element for the first time', () => {
40+
expect( () => {
41+
secureSourceElement( editor );
42+
} ).to.not.throw();
43+
} );
44+
45+
it( 'sets the property after initializing the editor', () => {
46+
secureSourceElement( editor );
47+
48+
expect( sourceElement.ckeditorInstance ).to.equal( editor );
49+
} );
50+
51+
it( 'removes the property after destroying the editor', () => {
52+
secureSourceElement( editor );
53+
54+
return editor.destroy()
55+
.then( () => {
56+
expect( sourceElement.ckeditorInstance ).to.be.undefined;
57+
} );
58+
} );
59+
60+
it( 'throws an error if the same element was used twice', () => {
61+
sourceElement.ckeditorInstance = 'foo';
62+
63+
expectToThrowCKEditorError( () => {
64+
secureSourceElement( editor );
65+
}, /^editor-source-element-already-used/, editor );
66+
} );
67+
} );

0 commit comments

Comments
 (0)