 if text
		 *         t: "...",  -> text data
		 *         a: {},     -> text attributes (optional)
		 *       },
		 *       ...
		 *     ]
		 *  }
		 *
		 * @memberOf Model
		 * @member {String} #data
		 */
		Object.defineProperty( editor.model, 'data', {
			get: () => {
				return liveDocumentData.get();
			},
			set: data => {
				return liveDocumentData.set( data );
			}
		} );
	}
}

/**
 * Maintains a "live" version of the main root element in a model document.
 *
 * By "live" we mean that changes to the model will just update the portions of the data that they touch.
 */
class LiveDocumentData {
	/**
	 * Creates an instance of the LiveDocumentData class.
	 *
	 * @param document {module:engine/model/document~Document} The document to be watching for changes.
	 */
	constructor( document ) {
		this.root = document.getRoot();

		// This is the "database" that holds the output text version for all nodes in the document model.
		const tree = this.tree = new Tree();

		document.on( 'change:data', () => {
			/* istanbul ignore next */
			if ( process.env.NODE_ENV !== 'production' ) {
				// Just set the following global (in the extension console) to see the performance log.
				if ( window.LOG_LIVE_MODEL_DATA ) {
					console.time( 'LiveModelData - process changes' );
				}
			}

			const changes = document.differ.getChanges();

			for ( const change of changes ) {
				const position = change.position;

				switch ( change.type ) {
					case 'insert' : {
						if ( change.name === '$text' ) {
							// It is hard to say precisely what a text insertion changed so we refresh
							// all texts (for simplicity).
							tree.refreshTexts( position.parent );
						} else {
							const element = position.nodeAfter;
							tree.insert( element );

							// If the element ended up between texts, there is a chance that it split them,
							// creating additional text nodes. As we don't have means to know that,
							// we refresh all texts (for simplicity).
							const isBetweenTexts = element.previousSibling && eleme