Jump to content

VisualEditor/Internals

From mediawiki.org

Key dependencies / utility classes

[edit]

OOjs

[edit]

VisualEditor uses OOjs for object orientation features, e.g. inheritance, mixins, static methods, factories and OO.EventEmitter events

OOUI

[edit]

VisualEditor uses OOUI as its UI library, most heavily within ve.ui.* but also within ve.ce.*.

UnicodeJS

[edit]

VisualEditor uses UnicodeJS to for standard Unicode facilities that require the Unicode Character Database (e.g. character classes and word breaks).

OO.EventEmitter

[edit]

OO.EventEmitter is actually part of OOJS, but is sufficiently important to deserve its own mention. It is used as VisualEditor's event system.

The most important thing to understand is that OO.EventEmitter works synchronously: event listeners run before the emit method returns. That has profound implications for the way document updates happen.

x = new OO.EventEmitter()
x.on( 'foo', function () {
    console.log( 'foo' );
} )
function test() {
    console.log( 'x' );
    x.emit( 'foo' );
    console.log( 'y' );
}
/*
> test();
> x
> foo
> y
*/

The codebase is very fundamentally engineered around the assumption that event handling is synchronous, with the resulting precisely defined execution order.

ve.EventSequencer

[edit]

Javascript keydown event listeners are fired before the event changes the DOM. Consider the case when the user presses an arrow key to change the selection:

contentEditableDiv.addEventListener( 'keydown', function onKeyDown( ev ) {
	// This runs before the browser processes the keydown.
} );

This creates a challenge if the listener needs to observe the change, e.g. to fixup the selection or to make UI changes corresponding to the new selection. The typical idiom is to use setTimeout to run code after the event:

contentEditableDiv.addEventListener( 'keydown', function onKeyDown( ev ) {
	setTimeout( function afterKeyDown() {
		// This runs after the browser processes the keydown.
	} );
} );

Unfortunately this is not very precise. setTimeout appends the function call to the Javascript task queue, which means many other things can happen before the function call. For instance, on Chromium 116 on Linux using an IME, pressing a single key can result in the following subsequent events firing before the setTimeout handler: keydown, keypress, compositionupdate, input, compositionend, keydown. Such subsequent events, and any listeners, can change the DOM before afterKeyDown gets a chance to run.

What is needed is more fine-grained control so that after-listeners can be run before any other code. ve.EventSequencer achieves this, by listening to multiple different events. Then, whenever any of the events fire, any pending after-listener is run.

var eventSequencer = new ve.EventSequencer( [
    'keydown', 'keypress', 'input',
    'compositionstart', 'compositionupdate', 'compositionend'
] );
eventSequencer.after( {
	keydown: function afterKeyDown() {
		// This runs as soon as possible after the
        // browser processes the keydown.
    }
} );

ve.utils

[edit]

Miscallaneous utility functions, e.g. to efficiently concatenate arrays in place, convert an HTML string into a HTMLDocument object, or simplify arrays of selection rectangles.

The data model

[edit]

Classes: ve.dm.*

See: VisualEditor/Internals/DM

The data model is optimized for transactional editing. This model is similar to an HTML token stream, however inline formatting is composed onto each character. This allows arbitrary slicing of content to be simple and efficient. The transaction system allows modifications of the document to be safe and reversible. Transactions are prepared against the current document state and then committed. Transactions can also be later rolled back, or "undone".

The ContentEditable view

[edit]

Classes: ve.ce.*

See: VisualEditor/Internals/CE

The CE view handles rendering, selection and input. The state of the data model is rendered into ContentEditable HTML. Only a highly limited set of operations are allowed to occur without intervention. Javascript listeners constantly watch the DOM for changes in the content, which are then sent to the model as transactions. Selection and input are normally allowed to happen natively, but many actions such as cursor movement or clipboard actions are overridden or quickly corrected. In certain cases selection and input are emulated in Javascript.

The CE HTML is optimized to achieve a high level of software control over the ContentEditable functionality, which means it is more elaborate than the simple rendering that would represent the data model in HTML in the most straightforward way.

The user interface controls

[edit]

Classes: ve.ui.*

See: VisualEditor/Internals/UI

Changes to document structure and inline formatting are accomplished by using user interface controls such as toolbars and inspectors. The basic toolbar floats at the top of the page above the content providing easy access to its tools independent of the document's length or current scroll position. Inspectors are lightweight inline dialogs that provide additional control over more complex formatting, such as link locations or template parameters.

The editing workflow

[edit]

Mutations that originate in the DM

[edit]

Mutations that originate in the ContentEditable surface

[edit]

Classes: ve.ce.SurfaceObserver

Complex mutations: Prepare-observe-fixup

[edit]

Copy and paste

[edit]

Synchronizing from the DM to the CE

[edit]

Classes: ve.dm.TreeModifier, ...

Startup

[edit]

Classes: ve.init.*

MediaWiki integration

[edit]

Repository: mediawiki/extensions/VisualEditor

Classes: ve.*.MW*

Parsoid HTML

[edit]

Round tripping wikitext

[edit]

Meta items

[edit]

Templates

[edit]