VisualEditor/Internals
Key dependencies / utility classes
editOOjs
editVisualEditor uses OOjs for object orientation features, e.g. inheritance, mixins, static methods, factories and OO.EventEmitter events
OOUI
editVisualEditor uses OOUI as its UI library, most heavily within ve.ui.* but also within ve.ce.*.
UnicodeJS
editVisualEditor uses UnicodeJS to for standard Unicode facilities that require the Unicode Character Database (e.g. character classes and word breaks).
OO.EventEmitter
editOO.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
editJavascript 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
editMiscallaneous 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
editClasses: 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
editClasses: 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
editClasses: 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
editMutations that originate in the DM
editMutations that originate in the ContentEditable surface
editClasses: ve.ce.SurfaceObserver
Complex mutations: Prepare-observe-fixup
editCopy and paste
editSynchronizing from the DM to the CE
editClasses: ve.dm.TreeModifier, ...
Startup
editClasses: ve.init.*
MediaWiki integration
editRepository: mediawiki/extensions/VisualEditor
Classes: ve.*.MW*