This page covers the execCommand API implementation in Ladybird, including how commands are registered and dispatched, the internal editing algorithms that manipulate the DOM, the contenteditable editing host system, and keyboard input routing for rich text editors. For form controls such as <input> and <textarea>, which also handle user text input but through a different path, see Form Controls and Input Elements.
Rich text editing is centered on the document.execCommand() JavaScript API, which routes through a command registry. Each registered command has an action function, optional state/value query functions, and metadata. Internal editing algorithms—canonicalizing whitespace, deleting selections, block-extending ranges, and applying inline or block formatting—are implemented separately from the command dispatch layer.
Data flow: execCommand dispatch
Sources: Libraries/LibWeb/Editing/ExecCommand.cpp20-141
All supported commands are described by the CommandDefinition struct defined in Libraries/LibWeb/Editing/Commands.h17-33 Each instance captures the command's behavior as a set of optional callbacks.
| Field | Purpose |
|---|---|
action | Executes the command; returns whether it succeeded |
indeterminate | Returns true if the command state is ambiguous (e.g. mixed bold/non-bold) |
state | Returns whether the command is currently active (e.g. is selection bold?) |
value | Returns the current effective value (e.g. current font size) |
relevant_css_property | CSS property this command maps to, used for value queries |
preserves_overrides | If true, overrides are recorded before and restored after if selection is collapsed |
mapped_value | The inputType value sent in the resulting InputEvent |
The registry is queried via find_command_definition(FlyString const&) declared in Libraries/LibWeb/Editing/Commands.h35
Sources: Libraries/LibWeb/Editing/Commands.h17-96
Document::exec_command in Libraries/LibWeb/Editing/ExecCommand.cpp20-141 is the sole entry point for both JavaScript-initiated commands and internally triggered ones.
Key behaviors:
InvalidStateError.m_inside_exec_command flag prevents re-entrant calls (e.g. from an input event handler).beforeinput event. The spec mandates firing it; however, as noted inline with AD-HOC comments, neither Chrome nor Firefox actually fire beforeinput for execCommand, and Ladybird follows their behavior.dom_tree_version() and character_data_version() before and after the action. If either changed, an InputEvent with inputType set to command_definition.mapped_value is fired at the affected editing host.preserves_overrides is true, record_current_overrides is called before the action and restore_states_and_values is called after if the resulting selection is collapsed.Sources: Libraries/LibWeb/Editing/ExecCommand.cpp20-141
All command action functions are declared in Libraries/LibWeb/Editing/Commands.h38-95 and implemented in Libraries/LibWeb/Editing/Commands.cpp The following table summarizes the supported commands and their key behavior:
| Command | Action Function | Notes |
|---|---|---|
bold | command_bold_action | Toggles font-weight: bold via set_the_selections_value |
italic | command_italic_action | Toggles font-style: italic |
underline | command_underline_action | Toggles text-decoration: underline |
strikethrough | command_strikethrough_action | Toggles text-decoration: line-through |
subscript | command_subscript_action | Applies <sub> |
superscript | command_superscript_action | Applies <sup> |
foreColor | command_fore_color_action | Sets foreground color; prepends # if not a valid CSS color |
backColor | command_back_color_action | Sets background color |
fontName | command_font_name_action | Sets font family |
fontSize | command_font_size_action | Maps numeric 1–7 to CSS keyword sizes (x-small…xxx-large) |
formatBlock | command_format_block_action | Wraps selection in a block element (e.g. <h1>, <p>) |
indent | command_indent_action | Increases indentation |
outdent | command_outdent_action | Decreases indentation |
insertText | command_insert_text_action | Inserts plain text at the current cursor/selection |
insertHTML | command_insert_html_action | Inserts raw HTML markup |
insertParagraph | command_insert_paragraph_action | Inserts a new paragraph |
insertLinebreak | command_insert_linebreak_action | Inserts a <br> |
insertOrderedList | command_insert_ordered_list_action | Toggles <ol> list |
insertUnorderedList | command_insert_unordered_list_action | Toggles <ul> list |
insertImage | command_insert_image_action | Inserts <img> with given URL |
insertHorizontalRule | command_insert_horizontal_rule_action | Inserts <hr> |
createLink | command_create_link_action | Wraps selection in <a href="..."> |
unlink | command_unlink_action | Removes ancestor <a> elements |
justifyLeft/Right/Center/Full | command_justify_*_action | Sets text-align on block containers |
delete | command_delete_action | Backward-delete at selection or cursor |
forwardDelete | command_forward_delete_action | Forward-delete at cursor |
selectAll | command_select_all_action | Selects all content in the editing host |
removeFormat | command_remove_format_action | Strips inline formatting |
styleWithCSS | command_style_with_css_action | Switches between CSS-style and element-style formatting |
defaultParagraphSeparator | command_default_paragraph_separator_action | Sets default block element to p or div |
Sources: Libraries/LibWeb/Editing/Commands.h38-95 Libraries/LibWeb/Editing/Commands.cpp34
All low-level DOM manipulation algorithms reside in the Web::Editing namespace, declared in Libraries/LibWeb/Editing/Internal/Algorithms.h and implemented in Libraries/LibWeb/Editing/Internal/Algorithms.cpp
Key algorithm relationships
Sources: Libraries/LibWeb/Editing/Internal/Algorithms.h52-140
canonicalize_whitespace(DOM::BoundaryPoint, bool fix_collapsed_space = true) normalizes the whitespace sequence around a boundary point. It scans backwards and forwards from the point to find any run of U+0020 or U+00A0 characters in non-pre whitespace contexts, then replaces them with a canonical sequence produced by canonical_space_sequence(length, non_breaking_start, non_breaking_end). This prevents inadvertent whitespace collapsing or expansion after editing operations.
It is called at the start of the delete command (Libraries/LibWeb/Editing/Commands.cpp146-147) and within delete_the_selection.
Sources: Libraries/LibWeb/Editing/Internal/Algorithms.cpp344-577
void delete_the_selection(Selection&, bool block_merging, bool strip_wrappers, Selection::Direction)
Removes the content covered by the selection. When block_merging is true, the blocks on either side of the deleted content are merged. The direction parameter controls whether the merge proceeds forward or backward, affecting which block's formatting is preserved.
Sources: Libraries/LibWeb/Editing/Internal/Algorithms.h60
block_extend_a_range(GC::Ref<DOM::Range>) → GC::Ref<DOM::Range> widens a range so that its start and end fall on block boundaries. It handles <li> ancestors specially, anchoring to the list item rather than the text node. Used by formatBlock, indent/outdent, and delete.
Sources: Libraries/LibWeb/Editing/Internal/Algorithms.cpp168-255
set_the_selections_value(DOM::Document&, FlyString const& command, Optional<Utf16String const&> value) is the core formatting dispatcher. It applies a formatting command to all formattable nodes in the active range. The algorithm:
record_current_states_and_values to capture current state.push_down_values to propagate the value to descendants, then force_the_value or clear_the_value on the node itself.restore_the_values_of_nodes to fix up any nodes that should not have been changed.Sources: Libraries/LibWeb/Editing/Internal/Algorithms.h116 Libraries/LibWeb/Editing/Internal/Algorithms.cpp
Two enums capture aggregate state about the current selection:
SelectionsListState (from selections_list_state(document)) determines whether the selection is entirely inside an <ol>, a <ul>, a mix of both, or neither. Used by insertOrderedList and insertUnorderedList to decide whether to toggle off or on.JustifyAlignment (from alignment_value_of_node(node)) resolves the computed text-align of a block node into one of four canonical values, accounting for start/end vs. LTR/RTL directionality. Used by justify_the_selection and the justifyLeft/justifyRight/justifyCenter/justifyFull commands.Sources: Libraries/LibWeb/Editing/Internal/Algorithms.h29-46 Libraries/LibWeb/Editing/Internal/Algorithms.cpp57-103
An editing host is any element with contenteditable="true" (or contenteditable="") set on it, or a Document in designMode. The editing algorithms all operate relative to a node's editing host—the nearest ancestor that is itself editable.
Key utilities from Algorithms.h:
| Function | Purpose |
|---|---|
is_in_same_editing_host(a, b) | Returns true if two nodes share the same editing host |
is_editable_or_editing_host(node) | Used to guard algorithm entry |
active_range(document) | Returns document.getSelection().range() |
fix_disallowed_ancestors_of_node(node) | Re-parents a node if it would be invalid in its current position |
Document::exec_command computes the affected editing host by comparing the editing hosts of the active range's start and end containers, picking the one that is not an ancestor of the other (Libraries/LibWeb/Editing/ExecCommand.cpp56-69).
Sources: Libraries/LibWeb/Editing/ExecCommand.cpp56-69 Libraries/LibWeb/Editing/Internal/Algorithms.h76-84
The EditingHostManager class (Libraries/LibWeb/DOM/EditingHostManager.h Libraries/LibWeb/DOM/EditingHostManager.cpp) is a JS::Cell that implements the InputEventsTarget interface. It acts as the bridge between raw keyboard events and exec_command for contenteditable elements.
EditingHostManager class relationships
When the user types in a contenteditable element, EditingHostManager::handle_insert is called with the input type and the typed string. It delegates directly to Document::exec_command(CommandNames::insertText, false, value) (Libraries/LibWeb/DOM/EditingHostManager.cpp37-47).
Similarly, handle_delete calls the delete or forwardDelete command, and handle_return_key invokes insertParagraph or insertLinebreak depending on context.
Cursor movement methods (move_cursor_to_start, increment_cursor_position_offset, etc.) manipulate the DOM Selection object directly without going through exec_command.
Sources: Libraries/LibWeb/DOM/EditingHostManager.cpp37-160 Libraries/LibWeb/DOM/EditingHostManager.h22-50 Libraries/LibWeb/DOM/InputEventsTarget.h
Document also exposes query_command_enabled, query_command_state, query_command_indeterminate, and query_command_value, all implemented in Libraries/LibWeb/Editing/ExecCommand.cpp143 These delegate to the corresponding fields of CommandDefinition.
query_command_enabled: Returns whether the command is both supported and can currently be executed (the active range must be in an editable context).query_command_state: Returns whether the command is currently "on" for the selection (e.g. bold → is the selection bold?).query_command_indeterminate: Returns whether the command has mixed state across the selection.query_command_value: Returns a string describing the current command value (e.g. the font size).All four functions refuse to operate on non-HTML documents and throw InvalidStateError in that case.
Sources: Libraries/LibWeb/Editing/ExecCommand.cpp143
Sources: Libraries/LibWeb/Editing/ExecCommand.cpp Libraries/LibWeb/Editing/Commands.h Libraries/LibWeb/Editing/Commands.cpp Libraries/LibWeb/Editing/Internal/Algorithms.h Libraries/LibWeb/Editing/Internal/Algorithms.cpp Libraries/LibWeb/DOM/EditingHostManager.h Libraries/LibWeb/DOM/EditingHostManager.cpp Libraries/LibWeb/DOM/InputEventsTarget.h
Refresh this wiki