Wiki source code of Macro Execution Rules

Last modified by Eleni Cojocariu on 2026/10/13 18:18

Hide last authors
Eleni Cojocariu 5.1 1 A macro does not decide when it runs, or in what state: the [[macro transformation>>doc:documentation.xs.dev.rendering-macros.macro-transformation.WebHome]] does, and it expects things of the macro in return. This page covers what a macro owes that engine; [[Rendering Macros>>doc:documentation.xs.dev.rendering-macros.WebHome]] covers what a macro is made of.
Eleni Cojocariu 2.1 2
3 == Restricted Mode ==
4
5 The transformation context can be set in **restricted mode**. It tells the macro that the rendering it is part of must not:
6
7 * modify the database or perform other privileged operations;
8 * perform expensive computations;
9 * perform any potentially dangerous operation, such as executing a script.
10
11 A macro doing any of these reads the flag, and then either does not execute at all or executes a reduced version of itself. Respecting it is the macro's own responsibility.
12
13 {{code language="java"}}
14 if (context.getTransformationContext().isRestricted())
15 {
16 // Do not execute, or leave out the part that is a concern.
17 }
18 {{/code}}
19
20 == Macro Preparation ==
21
22 The transformation can also ask a macro to **prepare** a ##MacroBlock## before executing it: pre-compute everything that is independent of any context and store it as an attribute of the block, so that each later execution reuses the result instead of computing it again. For a macro whose work is mostly context-independent, and whose block is executed many times, that is a large performance difference.
23
24 A macro opts in by overriding ##prepare##, a default method that otherwise does nothing:
25
26 {{code language="java"}}
27 default void prepare(MacroBlock macroBlock) throws MacroPreparationException
28 {{/code}}
29
30 What it stores must be **clonable or immutable**, because a prepared block can end up exposed in an unsafe environment.
31
32 == Isolated Execution ==
33
34 A macro is free to modify any part of the XDOM while it executes, inserting content or further macro calls elsewhere in the document. Doing so is discouraged, because the WYSIWYG editor converts the rendered content back to wiki syntax: every such modification is persisted in the page the author then saves.
35
Eleni Cojocariu 5.1 36 The engine pays for the possibility too: it has to search the XDOM again after every macro that may have changed it, instead of working off a queue, as [[Macro Transformation>>doc:documentation.xs.dev.rendering-macros.macro-transformation.WebHome]] describes.
Eleni Cojocariu 2.1 37
38 A macro may therefore declare its execution **isolated**: a promise that it will not modify the XDOM. A macro that executes transformations over its content in a way that gives those transformations access to the XDOM **must not** make that promise, because the macros it runs could modify the XDOM themselves.
39
40 The promise is made by overriding ##isExecutionIsolated##, which receives the same parameters and content as ##execute##, so the answer can depend on how the macro was called:
41
42 {{code language="java"}}
43 /**
44 * @param parameters the parameters with which the macro would be executed
45 * @param content the content with which the macro would be executed
46 * @return {@code true}, if executing the macro with the parameters definitely won't modify the XDOM,
47 * {@code false}, otherwise.
48 */
49 @Unstable
50 default boolean isExecutionIsolated(P parameters, String content)
51 {
52 return false;
53 }
54 {{/code}}
55
56 == Replacing the Macro Itself ==
57
58 The one modification of the XDOM that is routine is a macro replacing its own macro call, which is how a deprecated macro hands over to the macro that supersedes it. It builds the replacement ##MacroBlock##, swaps it into the parent of the old one, and returns no blocks of its own; the transformation recognises the situation and ignores the result of the execution.
59
60 {{code language="java"}}
61 @Override
62 public List<Block> execute(Object parameters, String content, MacroTransformationContext context)
63 throws MacroExecutionException
64 {
65 // Construct a new macro block to replace the old macro block.
66 MacroBlock oldMacroBlock = context.getCurrentMacroBlock();
67 Optional<String> oldValue = Optional.ofNullable(oldMacroBlock.getParameter("oldParameter"));
68 Map<String, String> newParameters = Map.of("param1", oldValue.orElse("default").replace("old", "new"));
69 MacroBlock replacement = new MacroBlock("replacement", newParameters, content, context.isInline());
70
71 // Replace the old macro block with the new macro block.
72 oldMacroBlock.getParent().replaceChild(replacement, oldMacroBlock);
73
74 return List.of();
75 }
76 {{/code}}
77
78 Declare correct content and parameter metadata on the macro being replaced even so, although none of it will ever be visible in the WYSIWYG editor: the [[macro refactoring>>doc:documentation.xs.dev.rendering-macros.java-macro-helpers.WebHome]] relies on that metadata to find the macros whose content has to be adjusted when a page is moved.

Get Connected