diff --git a/packages/ckeditor5-core/src/editor/editor.js b/packages/ckeditor5-core/src/editor/editor.js index 95e82222709..6aaef81f485 100644 --- a/packages/ckeditor5-core/src/editor/editor.js +++ b/packages/ckeditor5-core/src/editor/editor.js @@ -7,6 +7,8 @@ * @module core/editor/editor */ +/* global document */ + import Context from '../context'; import Config from '@ckeditor/ckeditor5-utils/src/config'; import EditingController from '@ckeditor/ckeditor5-engine/src/controller/editingcontroller'; @@ -175,6 +177,8 @@ export default class Editor { */ this.data = new DataController( this.model, stylesProcessor ); + const sourceElementRoot = config.sourceElementRoot ? config.sourceElementRoot : document; + /** * The {@link module:engine/controller/editingcontroller~EditingController editing controller}. * Controls user input and rendering the content for editing. @@ -182,7 +186,7 @@ export default class Editor { * @readonly * @member {module:engine/controller/editingcontroller~EditingController} */ - this.editing = new EditingController( this.model, stylesProcessor ); + this.editing = new EditingController( this.model, stylesProcessor, sourceElementRoot ); this.editing.view.document.bind( 'isReadOnly' ).to( this ); /** diff --git a/packages/ckeditor5-engine/src/controller/editingcontroller.js b/packages/ckeditor5-engine/src/controller/editingcontroller.js index f0d3f8caacd..ec9b0b1ec11 100644 --- a/packages/ckeditor5-engine/src/controller/editingcontroller.js +++ b/packages/ckeditor5-engine/src/controller/editingcontroller.js @@ -3,6 +3,8 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ +/* global document */ + /** * @module engine/controller/editingcontroller */ @@ -33,7 +35,7 @@ export default class EditingController { * @param {module:engine/model/model~Model} model Editing model. * @param {module:engine/view/stylesmap~StylesProcessor} stylesProcessor The styles processor instance. */ - constructor( model, stylesProcessor ) { + constructor( model, stylesProcessor, sourceElementRoot = document ) { /** * Editor model. * @@ -48,7 +50,7 @@ export default class EditingController { * @readonly * @member {module:engine/view/view~View} */ - this.view = new View( stylesProcessor ); + this.view = new View( stylesProcessor, sourceElementRoot ); /** * Mapper which describes the model-view binding. diff --git a/packages/ckeditor5-engine/src/view/domconverter.js b/packages/ckeditor5-engine/src/view/domconverter.js index 4beac20d1ac..8f0acdd016f 100644 --- a/packages/ckeditor5-engine/src/view/domconverter.js +++ b/packages/ckeditor5-engine/src/view/domconverter.js @@ -850,7 +850,9 @@ export default class DomConverter { * @returns {Boolean} */ isDomSelectionBackward( selection ) { - if ( selection.isCollapsed ) { + // Due to a bug with .isCollapsed always returning true when the selection is from a shadowRoot, also check that the selection + // has no ranges or its range is collapsed. More info: https://bugs.chromium.org/p/chromium/issues/detail?id=447523 + if ( selection.isCollapsed && ( selection.rangeCount === 0 || selection.getRangeAt( 0 ).collapsed ) ) { return false; } diff --git a/packages/ckeditor5-engine/src/view/observer/selectionobserver.js b/packages/ckeditor5-engine/src/view/observer/selectionobserver.js index 4b3193c7762..636a0efb2a1 100644 --- a/packages/ckeditor5-engine/src/view/observer/selectionobserver.js +++ b/packages/ckeditor5-engine/src/view/observer/selectionobserver.js @@ -7,7 +7,7 @@ * @module engine/view/observer/selectionobserver */ -/* global setInterval, clearInterval */ +/* global setInterval, clearInterval, document */ import Observer from './observer'; import MutationObserver from './mutationobserver'; @@ -26,7 +26,7 @@ import { debounce } from 'lodash-es'; * @extends module:engine/view/observer/observer~Observer */ export default class SelectionObserver extends Observer { - constructor( view ) { + constructor( view, sourceElementRoot = document ) { super( view ); /** @@ -87,6 +87,8 @@ export default class SelectionObserver extends Observer { * @member {Number} module:engine/view/observer/selectionobserver~SelectionObserver#_loopbackCounter */ this._loopbackCounter = 0; + + this.sourceElementRoot = sourceElementRoot; } /** @@ -135,7 +137,12 @@ export default class SelectionObserver extends Observer { // If there were mutations then the view will be re-rendered by the mutation observer and selection // will be updated, so selections will equal and event will not be fired, as expected. - const domSelection = domDocument.defaultView.getSelection(); + let domSelection; + if ( this.sourceElementRoot === domDocument ) { + domSelection = domDocument.defaultView.getSelection(); + } else { + domSelection = this.sourceElementRoot.getSelection(); + } const newViewSelection = this.domConverter.domSelectionToView( domSelection ); // Do not convert selection change if the new view selection has no ranges in it. diff --git a/packages/ckeditor5-engine/src/view/view.js b/packages/ckeditor5-engine/src/view/view.js index 15fdcb4961c..69b51e71cf8 100644 --- a/packages/ckeditor5-engine/src/view/view.js +++ b/packages/ckeditor5-engine/src/view/view.js @@ -3,6 +3,8 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ +/* global document */ + /** * @module engine/view/view */ @@ -65,7 +67,7 @@ export default class View { /** * @param {module:engine/view/stylesmap~StylesProcessor} stylesProcessor The styles processor instance. */ - constructor( stylesProcessor ) { + constructor( stylesProcessor, sourceElementRoot = document ) { /** * Instance of the {@link module:engine/view/document~Document} associated with this view controller. * @@ -179,7 +181,7 @@ export default class View { // Add default observers. this.addObserver( MutationObserver ); - this.addObserver( SelectionObserver ); + this.addObserver( SelectionObserver, sourceElementRoot ); this.addObserver( FocusObserver ); this.addObserver( KeyObserver ); this.addObserver( FakeSelectionObserver ); @@ -333,14 +335,14 @@ export default class View { * Should create an instance inheriting from {@link module:engine/view/observer/observer~Observer}. * @returns {module:engine/view/observer/observer~Observer} Added observer instance. */ - addObserver( Observer ) { + addObserver( Observer, sourceElementRoot ) { let observer = this._observers.get( Observer ); if ( observer ) { return observer; } - observer = new Observer( this ); + observer = new Observer( this, sourceElementRoot ); this._observers.set( Observer, observer );