DOM Triggers and Navigation
Register DOM event callbacks once in C++ and keep them active across page navigations.
On this page
You can define DOM event bindings in C++ that persist across page loads (and re-use them across multiple Views).
Wiring Every Page
A dom::Triggers set holds your DOM event wiring in one place. Attach the set to a View before loading content so every page the View loads gets it:
dom::Triggers ui;
ui.On("#save", "click", [] { SaveGame(); });
ui.OnDOMReady([](dom::Document document) {
document.getElementById("status").textContent = "Ready";
});
if (ui.AttachTo(view.get()))
view->LoadURL("file:///app.html");
Kinds of Wiring
The set supports event listeners for matched elements along with lifecycle hooks for page loading:
| Registration | Runs |
|---|---|
On(selector, type, callback) |
When events fire on elements matching the selector, including elements added later (the callback takes the matched element, both the event and element, or nothing) |
OnDOMReady(callback) |
Once per page after parsing finishes (the place to find elements and keep handles, though the set never sees the page's own DOMContentLoaded) |
OnRestore(callback) |
Each time a page comes back from the back-forward cache |
Events That Don't Bubble
Events that don't bubble (eg, focus, blur, and invalid) reach On() only with dom::Capture. Pass the flag as the last parameter to handle them:
ui.On("input", "focus", [](dom::Element field) {
field.classList.add("editing");
}, dom::Capture);
Choosing Which Pages Get the Wiring
By default only your own content gets the set (file:// pages and View::LoadHTML()). Passing origin rules replaces that default, so add "file://*" if you want to keep local pages (see Extending JavaScript with Native API for rule syntax).
Pass dom::AllFrames to include subframes (which runs OnDOMReady for each frame's document):
bool attached = ui.AttachTo(view.get(), {
.flags = dom::AllFrames,
.origin_rules = { "https://*.mygame.com", "file://*" } });
AttachTo() returns false (and logs why) when a rule fails to parse.
Filtering Pages in Code
For decisions origin rules can't express (such as checking a user setting or admitting an opaque origin), set an injection filter on the View:
dom::SetInjectionFilter(view.get(), [](const dom::InjectionRequest& request) {
return request.rules_allow && request.is_main_frame;
});
Origin rules run first and pass their verdict in rules_allow. Return true to add the listeners or false to skip the page— the return value overrides the origin rules either way.
🚧 Call on the Renderer's Thread
Call
dom::SetInjectionFilter()on the Renderer's thread. The filter callback runs on that thread too.
To remove the filter and let origin rules decide on their own again, call dom::ClearInjectionFilter(view.get()).
Navigation and the Back-Forward Cache
Same-document navigations like a hash change or pushState aren't new pages (handles stay valid and nothing is re-added). A page restored from the back-forward cache (Config::page_cache_size above 0) gets its listeners back before pageshow, but DOM-ready hooks don't run again— use OnRestore() for work needed on every restore:
ui.OnRestore([](dom::Document document) {
document.getElementById("status").textContent = "Welcome back";
});
📘 Direct Listeners Don't Survive Back and Forward
Listeners added with
addEventListener()are gone when a page is restored. Use adom::Triggersset instead, or add them again inOnRestore().
Changing the Wiring
Registrations can't be removed one at a time, and any handler you add after attaching only reaches the next page the View loads. Call DetachFrom() to remove the whole set from a View right away:
ui.DetachFrom(view.get());
Storing a Set in an Object
Store a dom::Triggers set as a member of any class whose callbacks capture this:
class Hud {
public:
explicit Hud(View* view) {
ui_.On("#close", "click", [this] { Close(); });
if (!ui_.AttachTo(view))
Log("an origin rule failed to parse");
}
private:
void Close() {}
dom::Triggers ui_;
};
An attached View keeps the set only while the dom::Triggers object exists. Destroying the object detaches it from every View— its listeners stop right away, so callbacks can safely call the owner's methods.
The library destroys registered callables (and anything they capture) on the Renderer's thread. This cleanup runs during destruction when nothing else references the set, or during a later Renderer::Update().
Triggers or addEventListener?
Use dom::Triggers for listeners that need to work across multiple page navigations (or need to be used across multiple Views), and use addEventListener() for listeners tied to a single page's state:
dom::Triggers |
addEventListener() |
|
|---|---|---|
| Pages | Every page of every attached View | The current page only |
| Targets | Elements matching a selector, now and later | One element, document, or window |
| Back and forward | Re-added on restore | Gone after a restore |
| Removing | The whole set, with DetachFrom() |
One listener, with Remove() |
| Use it for | UI wiring that must always work | Listeners tied to one page's state |