Skip to content

Commit d874050

Browse files
authored
Merge pull request #17296 from ckeditor/cc/performance-offsets
Feature (engine): Introduced `getChildAtOffset()` method for `model.Element` and `model.DocumentFragment`. Feature (engine): Introduced `Position#isValid()` to check whether the position exists in current model tree. Other (engine): Node index and offset related values are now cached in model `Node` and `NodeList` to improve performance. Internal (engine): Used `Position#isValid()` to better validate selection ranges. Tests (engine): Changed error messages expected in some tests. Now different errors may be thrown than earlier because internal execution logic changed a bit. New error codes are more precise.
2 parents 56cce90 + 055753f commit d874050

13 files changed

+350
-143
lines changed

packages/ckeditor5-engine/src/model/document.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,8 @@ export default class Document extends /* #__PURE__ */ EmitterMixin() {
433433
* @returns `true` if `range` is valid, `false` otherwise.
434434
*/
435435
public _validateSelectionRange( range: Range ): boolean {
436-
return validateTextNodePosition( range.start ) && validateTextNodePosition( range.end );
436+
return range.start.isValid() && range.end.isValid() &&
437+
validateTextNodePosition( range.start ) && validateTextNodePosition( range.end );
437438
}
438439

439440
/**

packages/ckeditor5-engine/src/model/documentfragment.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,23 @@ export default class DocumentFragment extends TypeCheckable implements Iterable<
149149
/**
150150
* Gets the child at the given index. Returns `null` if incorrect index was passed.
151151
*
152-
* @param index Index of child.
152+
* @param index Index in this document fragment.
153153
* @returns Child node.
154154
*/
155155
public getChild( index: number ): Node | null {
156156
return this._children.getNode( index );
157157
}
158158

159+
/**
160+
* Gets the child at the given offset. Returns `null` if incorrect index was passed.
161+
*
162+
* @param offset Offset in this document fragment.
163+
* @returns Child node.
164+
*/
165+
public getChildAtOffset( offset: number ): Node | null {
166+
return this._children.getNodeAtOffset( offset );
167+
}
168+
159169
/**
160170
* Returns an iterator that iterates over all of this document fragment's children.
161171
*/
@@ -208,8 +218,8 @@ export default class DocumentFragment extends TypeCheckable implements Iterable<
208218
// eslint-disable-next-line @typescript-eslint/no-this-alias, consistent-this
209219
let node: Node | DocumentFragment = this;
210220

211-
for ( const index of relativePath ) {
212-
node = ( node as Element | DocumentFragment ).getChild( ( node as Element | DocumentFragment ).offsetToIndex( index ) )!;
221+
for ( const offset of relativePath ) {
222+
node = ( node as Element | DocumentFragment ).getChildAtOffset( offset )!;
213223
}
214224

215225
return node;

packages/ckeditor5-engine/src/model/element.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,25 @@ export default class Element extends Node {
8282
}
8383

8484
/**
85-
* Gets the child at the given index.
85+
* Gets the child at the given index. Returns `null` if incorrect index was passed.
86+
*
87+
* @param index Index in this element.
88+
* @returns Child node.
8689
*/
8790
public getChild( index: number ): Node | null {
8891
return this._children.getNode( index );
8992
}
9093

94+
/**
95+
* Gets the child at the given offset. Returns `null` if incorrect index was passed.
96+
*
97+
* @param offset Offset in this element.
98+
* @returns Child node.
99+
*/
100+
public getChildAtOffset( offset: number ): Node | null {
101+
return this._children.getNodeAtOffset( offset );
102+
}
103+
91104
/**
92105
* Returns an iterator that iterates over all of this element's children.
93106
*/
@@ -153,8 +166,8 @@ export default class Element extends Node {
153166
// eslint-disable-next-line @typescript-eslint/no-this-alias, consistent-this
154167
let node: Node = this;
155168

156-
for ( const index of relativePath ) {
157-
node = ( node as Element ).getChild( ( node as Element ).offsetToIndex( index ) )!;
169+
for ( const offset of relativePath ) {
170+
node = ( node as Element ).getChildAtOffset( offset )!;
158171
}
159172

160173
return node;

packages/ckeditor5-engine/src/model/node.ts

+26-42
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,20 @@ export default abstract class Node extends TypeCheckable {
6363
*/
6464
private _attrs: Map<string, unknown>;
6565

66+
/**
67+
* Index of this node in its parent or `null` if the node has no parent.
68+
*
69+
* @internal
70+
*/
71+
public _index: number | null = null;
72+
73+
/**
74+
* Offset at which this node starts in its parent or `null` if the node has no parent.
75+
*
76+
* @internal
77+
*/
78+
public _startOffset: number | null = null;
79+
6680
/**
6781
* Creates a model node.
6882
*
@@ -85,66 +99,42 @@ export default abstract class Node extends TypeCheckable {
8599

86100
/**
87101
* Index of this node in its parent or `null` if the node has no parent.
88-
*
89-
* Accessing this property throws an error if this node's parent element does not contain it.
90-
* This means that model tree got broken.
91102
*/
92103
public get index(): number | null {
93-
let pos;
94-
95-
if ( !this.parent ) {
96-
return null;
97-
}
98-
99-
if ( ( pos = this.parent.getChildIndex( this ) ) === null ) {
100-
throw new CKEditorError( 'model-node-not-found-in-parent', this );
101-
}
102-
103-
return pos;
104+
return this._index;
104105
}
105106

106107
/**
107108
* Offset at which this node starts in its parent. It is equal to the sum of {@link #offsetSize offsetSize}
108109
* of all its previous siblings. Equals to `null` if node has no parent.
109-
*
110-
* Accessing this property throws an error if this node's parent element does not contain it.
111-
* This means that model tree got broken.
112110
*/
113111
public get startOffset(): number | null {
114-
let pos;
115-
116-
if ( !this.parent ) {
117-
return null;
118-
}
119-
120-
if ( ( pos = this.parent.getChildStartOffset( this ) ) === null ) {
121-
throw new CKEditorError( 'model-node-not-found-in-parent', this );
122-
}
123-
124-
return pos;
112+
return this._startOffset;
125113
}
126114

127115
/**
128-
* Offset size of this node. Represents how much "offset space" is occupied by the node in it's parent.
129-
* It is important for {@link module:engine/model/position~Position position}. When node has `offsetSize` greater than `1`, position
130-
* can be placed between that node start and end. `offsetSize` greater than `1` is for nodes that represents more
131-
* than one entity, i.e. {@link module:engine/model/text~Text text node}.
116+
* Offset size of this node.
117+
*
118+
* Represents how much "offset space" is occupied by the node in its parent. It is important for
119+
* {@link module:engine/model/position~Position position}. When node has `offsetSize` greater than `1`, position can be placed between
120+
* that node start and end. `offsetSize` greater than `1` is for nodes that represents more than one entity, i.e.
121+
* a {@link module:engine/model/text~Text text node}.
132122
*/
133123
public get offsetSize(): number {
134124
return 1;
135125
}
136126

137127
/**
138-
* Offset at which this node ends in it's parent. It is equal to the sum of this node's
128+
* Offset at which this node ends in its parent. It is equal to the sum of this node's
139129
* {@link module:engine/model/node~Node#startOffset start offset} and {@link #offsetSize offset size}.
140130
* Equals to `null` if the node has no parent.
141131
*/
142132
public get endOffset(): number | null {
143-
if ( !this.parent ) {
133+
if ( this.startOffset === null ) {
144134
return null;
145135
}
146136

147-
return this.startOffset! + this.offsetSize;
137+
return this.startOffset + this.offsetSize;
148138
}
149139

150140
/**
@@ -387,7 +377,7 @@ export default abstract class Node extends TypeCheckable {
387377
}
388378

389379
/**
390-
* Removes this node from it's parent.
380+
* Removes this node from its parent.
391381
*
392382
* @internal
393383
* @see module:engine/model/writer~Writer#remove
@@ -448,12 +438,6 @@ Node.prototype.is = function( type: string ): boolean {
448438
return type === 'node' || type === 'model:node';
449439
};
450440

451-
/**
452-
* The node's parent does not contain this node.
453-
*
454-
* @error model-node-not-found-in-parent
455-
*/
456-
457441
/**
458442
* Node's attributes. See {@link module:utils/tomap~toMap} for a list of accepted values.
459443
*/

0 commit comments

Comments
 (0)