docs
Loading...
Searching...
No Matches
Document.h
Go to the documentation of this file.
1///
2/// Copyright (C) 2026 Ultralight, Inc. All rights reserved.
3/// A license is required for commercial use. https://ultralig.ht
4///
5#pragma once
6#include <Ultralight/CAPI/CAPI_DOMDocument.h>
8#include <Ultralight/dom/detail/Receivers.h>
9#include <Ultralight/CAPI/CAPI_DOMEvent.h>
10#include <Ultralight/View.h>
16
17#include <string_view>
18#include <type_traits>
19#include <utility>
20
21namespace ultralight {
22namespace dom {
23
24class Selection;
25class Window;
26
27///
28/// The root of a page's DOM tree in a View or frame.
29///
30/// dom::Document provides native C++ access to a page's DOM tree. It's the starting point for
31/// querying elements and updating the user interface rendered by a View.
32///
33/// You typically obtain the document during page loads and keep handles to the elements you want to
34/// modify later.
35///
36/// The following listener stores a score element once the main frame's document is parsed and
37/// ready:
38///
39/// ```
40/// class Hud : public LoadListener {
41/// public:
42/// void OnDOMReady(View* caller, uint64_t frame_id, bool is_main_frame,
43/// const String& url) override {
44/// if (!is_main_frame)
45/// return;
46/// dom::Document document(caller);
47/// score_ = document.getElementById("score");
48/// }
49///
50/// void SetScore(int score) { score_.textContent = std::to_string(score); }
51///
52/// private:
53/// dom::Element score_;
54/// };
55/// ```
56///
57/// ## Getting a Document
58///
59/// You obtain a document from its owning View or frame:
60///
61/// - **Main frame documents come from the View.** Passing a View pointer to the Document
62/// constructor returns the active document of the top-level frame.
63/// - **Subframe documents come from their elements.** Calling
64/// dom::HTMLIFrameElement::contentDocument() on a frame element returns the document loaded
65/// inside that frame.
66///
67/// You should construct the main frame's document inside LoadListener::OnDOMReady(), where the new
68/// document is in place. Before the first page loads and while a new page is loading, the
69/// constructor returns the previous document, which goes away when the new page replaces it.
70///
71/// LoadListener::OnDOMReady() runs for every frame on the page. Check the `is_main_frame` parameter
72/// to ensure top-level setup logic runs only for the main frame.
73///
74/// Navigating to a new page creates a new document-- handles from the previous page belong to a
75/// page that is gone (dom::Element covers handle states).
76///
77/// ## Relationship to Nodes
78///
79/// dom::Document is a distinct type that doesn't inherit from dom::Node. Because the document isn't
80/// a node, the root element has no parent in the DOM tree.
81///
82/// On the root element, parentNode() is empty and ownerDocument() gets back to the document:
83///
84/// ```
85/// dom::Element root = document.documentElement();
86///
87/// root.parentNode(); // empty
88/// root.ownerDocument(); // the document
89/// ```
90///
91/// ## Document Events
92///
93/// You can listen for events on the document with addEventListener() or handle matching descendants
94/// using On(). Events dispatched on elements throughout the page bubble up to the document unless
95/// stopped along the way.
96///
97/// Page `load` and viewport `resize` events target the window rather than the document, so document
98/// listeners never receive them. Call defaultView() to obtain the page's dom::Window when you need
99/// to handle window-level events.
100///
101/// @note Don't add a `DOMContentLoaded` listener in LoadListener::OnDOMReady(). The event has
102/// already fired by the time the callback runs, so a listener added there never fires.
103///
104/// @see dom::Element, dom::Node, dom::Window, dom::Triggers, LoadListener::OnDOMReady()
105///
106class Document {
107 public:
108 ///
109 /// The document's title (title), the text of its `<title>` element with whitespace collapsed.
110 ///
111 /// Assign to replace the text of the `<title>` element. If there's no `<title>`, one is added
112 /// to the `<head>` (nothing happens if there's no `<head>` either).
113 ///
114 detail::DocumentStringProp<detail::DocumentTitleTag> title;
115
116 ///
117 /// Create an empty Document.
118 ///
119 Document() = default;
120
121 ///
122 /// Get the document of a View's main frame.
123 ///
124 /// @param view The View (a nullptr gives an empty Document).
125 ///
126 /// @note This is the document the frame has now. Before the first page loads, and while a new
127 /// page is loading, that's the previous document, which goes away when the new page
128 /// replaces it. Getting it in LoadListener::OnDOMReady() makes sure it's your page.
129 ///
130 explicit Document(View* view) : handle_(view ? view->GetDOMDocument() : nullptr) {}
131
132 ///
133 /// Copy constructor (both handles refer to the same document).
134 ///
135 /// @param other The Document to copy.
136 ///
137 Document(const Document& other)
138 : handle_(other.handle_ ? ulCreateDOMDocumentRef(other.handle_) : nullptr) {}
139
140 ///
141 /// Move constructor (`other` becomes empty).
142 ///
143 /// @param other The Document to move from.
144 ///
145 Document(Document&& other) noexcept : handle_(other.handle_) { other.handle_ = nullptr; }
146
147 ///
148 /// Assignment (copies or moves).
149 ///
150 /// @param other The Document to assign from.
151 ///
152 /// @return Returns this Document.
153 ///
154 Document& operator=(Document other) noexcept {
155 std::swap(handle_, other.handle_);
156 return *this;
157 }
158
159 ///
160 /// Destroy this handle (the document itself isn't affected).
161 ///
162 ~Document() { ulDestroyDOMDocument(handle_); }
163
164 ///
165 /// Whether or not this Document is valid (see IsAlive()).
166 ///
167 explicit operator bool() const { return IsAlive(); }
168
169 ///
170 /// Whether or not this Document is empty (it holds no handle).
171 ///
172 bool IsEmpty() const { return handle_ == nullptr; }
173
174 ///
175 /// Whether or not this Document is valid (it isn't empty and its page is still alive).
176 ///
177 /// @note Safe to call from any thread.
178 ///
179 bool IsAlive() const { return handle_ && ulDOMDocumentIsAlive(handle_); }
180
181 // --- Lookup -----------------------------------------------------------------------------
182
183 ///
184 /// Find the element with a certain id (getElementById).
185 ///
186 /// @param id The id to look for.
187 ///
188 /// @return Returns the element (empty if no element has that id).
189 ///
190 Element getElementById(std::string_view id) const {
191 detail::CString i(id);
192 return Element::Adopt(ulDOMDocumentGetElementById(handle_, i.c_str()));
193 }
194
195 ///
196 /// Find the first element matching a CSS selector (querySelector).
197 ///
198 /// @param selectors A CSS selector list (eg, `.item > a[href]`).
199 ///
200 /// @return Returns the first match in document order (empty if nothing matches, the selector
201 /// is malformed, or the page is gone).
202 ///
203 Element querySelector(std::string_view selectors) const {
204 detail::CString s(selectors);
205 return Element::Adopt(ulDOMDocumentQuerySelector(handle_, s.c_str(), nullptr));
206 }
207
208 ///
209 /// Same as querySelector(), but returns a Result with the reason for a failure (eg, a
210 /// SyntaxError for a malformed selector).
211 ///
212 /// @return Returns the first match in document order (an empty Element if nothing matches,
213 /// which isn't an error). Fails with a SyntaxError if the selector is malformed.
214 ///
215 [[nodiscard]] Result<Element> querySelector(std::string_view selectors, Checked_t) const {
216 if (IsEmpty())
217 return detail::EmptyHandleError();
218 detail::CString s(selectors);
219 detail::ErrorScope error;
220 ULDOMElement result = ulDOMDocumentQuerySelector(handle_, s.c_str(), error.out());
221 if (result)
222 return Element::Adopt(result);
223 if (error.has_exception())
224 return Unexpected<Error>(error.TakeError());
225 if (IsAlive())
226 return Element(); // No match on a live page: success with an empty handle.
227 return Unexpected<Error>(Error::PageGone());
228 }
229
230 ///
231 /// Find every element matching a CSS selector (querySelectorAll).
232 ///
233 /// The list is a snapshot, so later changes to the page don't change it.
234 ///
235 /// @param selectors A CSS selector list (eg, `.item > a[href]`).
236 ///
237 /// @return Returns the matches in document order (an empty list if nothing matches, the
238 /// selector is malformed, or the page is gone).
239 ///
240 ElementList querySelectorAll(std::string_view selectors) const {
241 detail::CString s(selectors);
242 return ElementList::Adopt(ulDOMDocumentQuerySelectorAll(handle_, s.c_str(), nullptr));
243 }
244
245 ///
246 /// Same as querySelectorAll(), but returns a Result with the reason for a failure (eg, a
247 /// SyntaxError for a malformed selector).
248 ///
249 /// @return Returns the matches in document order (an empty list if nothing matches). Fails
250 /// with a SyntaxError if the selector is malformed.
251 ///
252 [[nodiscard]] Result<ElementList> querySelectorAll(std::string_view selectors,
253 Checked_t) const {
254 if (IsEmpty())
255 return detail::EmptyHandleError();
256 detail::CString s(selectors);
257 detail::ErrorScope error;
258 ULDOMElementList result = ulDOMDocumentQuerySelectorAll(handle_, s.c_str(), error.out());
259 if (result)
260 return ElementList::Adopt(result);
261 return Unexpected<Error>(error.TakeError());
262 }
263
264 ///
265 /// Find the topmost element at a point in the viewport (elementFromPoint).
266 ///
267 /// @param x The horizontal position, in CSS pixels from the viewport's left edge.
268 ///
269 /// @param y The vertical position, in CSS pixels from the viewport's top edge.
270 ///
271 /// @return Returns the topmost element at that point (empty if the point is outside the
272 /// viewport).
273 ///
274 /// @note These are the same coordinates as event client positions and
275 /// Element::getBoundingClientRect(). To convert from View pixels, divide by
276 /// View::device_scale() (an 800x600 View with a device scale of 2.0 is 400x300 CSS
277 /// pixels).
278 ///
279 Element elementFromPoint(double x, double y) const {
280 return Element::Adopt(ulDOMDocumentElementFromPoint(handle_, x, y));
281 }
282
283 ///
284 /// Find every element at a point in the viewport, topmost first (elementsFromPoint).
285 ///
286 /// The list starts with the element elementFromPoint() returns and continues with the elements
287 /// painted below it. It ends with the root `<html>` element.
288 ///
289 /// @param x The horizontal position, in CSS pixels from the viewport's left edge.
290 ///
291 /// @param y The vertical position, in CSS pixels from the viewport's top edge.
292 ///
293 /// @return Returns the elements, topmost first (an empty list if the point is outside the
294 /// viewport).
295 ///
296 /// @note These are the same coordinates elementFromPoint() takes. Like that method, this
297 /// updates the layout first if the page has pending changes.
298 ///
299 ElementList elementsFromPoint(double x, double y) const {
300 return ElementList::Adopt(ulDOMDocumentElementsFromPoint(handle_, x, y));
301 }
302
303 // --- Well-known elements (read-only properties are calls) --------------------------------
304
305 ///
306 /// Get the document's root element (documentElement).
307 ///
308 /// @return Returns the root element (empty if there's none). In an HTML document it's the
309 /// `<html>` element.
310 ///
312 return Element::Adopt(ulDOMDocumentGetDocumentElement(handle_));
313 }
314
315 ///
316 /// Get the document's body element (body).
317 ///
318 /// @return Returns the `<body>` element (empty if there's none).
319 ///
320 Element body() const { return Element::Adopt(ulDOMDocumentGetBody(handle_)); }
321
322 ///
323 /// Get the document's head element (head).
324 ///
325 /// @return Returns the `<head>` element (empty if there's none).
326 ///
327 Element head() const { return Element::Adopt(ulDOMDocumentGetHead(handle_)); }
328
329 ///
330 /// Get the element that has keyboard focus (activeElement).
331 ///
332 /// @return Returns the focused element. When nothing has focus this is the body (empty if
333 /// there's no body).
334 ///
336 return Element::Adopt(ulDOMDocumentGetActiveElement(handle_));
337 }
338
339 ///
340 /// Whether or not the document has keyboard focus (hasFocus).
341 ///
342 /// This is true when the View has focus (see View::Focus()) and the focused frame is this
343 /// document's frame or one of its subframes.
344 ///
345 bool hasFocus() const { return ulDOMDocumentHasFocus(handle_); }
346
347 // --- Traversal (read-only properties are calls) -------------------------------------------
348
349 ///
350 /// Get the document's first child of any kind (firstChild).
351 ///
352 /// @return Returns the first child (eg, the doctype, a comment, or the root element; empty if
353 /// there's none).
354 ///
355 Node firstChild() const { return Node::Adopt(ulDOMDocumentGetFirstChild(handle_)); }
356
357 ///
358 /// Get the document's last child of any kind (lastChild).
359 ///
360 /// @return Returns the last child (empty if there's none).
361 ///
362 Node lastChild() const { return Node::Adopt(ulDOMDocumentGetLastChild(handle_)); }
363
364 ///
365 /// Get the document's first child element (firstElementChild).
366 ///
367 /// @return Returns the first child element, which is the root element (empty if there's none).
368 ///
370 return Element::Adopt(ulDOMDocumentGetFirstElementChild(handle_));
371 }
372
373 ///
374 /// Get the document's last child element (lastElementChild).
375 ///
376 /// @return Returns the last child element, which is the root element (empty if there's none).
377 ///
379 return Element::Adopt(ulDOMDocumentGetLastElementChild(handle_));
380 }
381
382 ///
383 /// Get the number of the document's child elements (childElementCount).
384 ///
385 /// @return Returns the number of child elements (1 for a document with a root element).
386 ///
387 size_t childElementCount() const { return ulDOMDocumentGetChildElementCount(handle_); }
388
389 ///
390 /// Get the document's child elements in order (children).
391 ///
392 /// @return Returns the child elements (an empty list if there are none).
393 ///
394 /// @note Unlike the web's live collection, the list doesn't change when the document does. Call
395 /// this again to see later changes.
396 ///
398 return ElementList::Adopt(ulDOMDocumentGetChildren(handle_));
399 }
400
401 ///
402 /// Get the document's children of every kind in order (childNodes).
403 ///
404 /// @return Returns the children, including the doctype and any comments outside the root
405 /// element (an empty list if there are none).
406 ///
407 /// @note The list is a snapshot, where the web's childNodes is live. Call this again to see
408 /// later changes.
409 ///
410 NodeList childNodes() const { return NodeList::Adopt(ulDOMDocumentGetChildNodes(handle_)); }
411
412 ///
413 /// Whether or not the document has any children (hasChildNodes).
414 ///
415 /// @return Returns true if the document has at least one child node.
416 ///
417 bool hasChildNodes() const { return ulDOMDocumentHasChildNodes(handle_); }
418
419 ///
420 /// Get the document's window (defaultView).
421 ///
422 /// The window holds the viewport's size and scroll position. It receives the page's `load` and
423 /// the viewport's `resize` events.
424 ///
425 /// @return Returns the window.
426 ///
427 /// @note Include `<Ultralight/dom/Window.h>` (or `<Ultralight/DOM.h>`) to call this.
428 ///
429 Window defaultView() const;
430
431 ///
432 /// Get the document's selection (getSelection).
433 ///
434 /// The selection is the highlighted text or the caret on the page. It's the same selection
435 /// Window::getSelection() returns.
436 ///
437 /// @return Returns the selection.
438 ///
439 /// @note Include `<Ultralight/dom/Selection.h>` (or `<Ultralight/DOM.h>`) to call this.
440 ///
441 Selection getSelection() const;
442
443 // --- Creation -----------------------------------------------------------------------------
444
445 ///
446 /// Create an element (createElement).
447 ///
448 /// The new element belongs to this document but isn't in the page yet. Add it with
449 /// Element::appendChild() or another insertion method.
450 ///
451 /// @param tag_name The tag name (eg, `div`). In an HTML document it's lowercased.
452 ///
453 /// @return Returns the new element (empty if `tag_name` isn't a valid element name).
454 ///
455 Element createElement(std::string_view tag_name) const {
456 detail::CString t(tag_name);
457 return Element::Adopt(ulDOMDocumentCreateElement(handle_, t.c_str(), nullptr));
458 }
459
460 ///
461 /// Create a text node (createTextNode).
462 ///
463 /// The new node belongs to this document but isn't in the page yet. Add it with
464 /// Element::appendChild() or another insertion method. The text is never parsed as markup.
465 ///
466 /// @param data The text.
467 ///
468 /// @return Returns the new text node.
469 ///
470 Node createTextNode(std::string_view data) const {
471 return Node::Adopt(
472 ulDOMDocumentCreateTextNode(handle_, data.data() ? data.data() : "", data.size()));
473 }
474
475 ///
476 /// Create a comment node (createComment).
477 ///
478 /// The new node belongs to this document but isn't in the page yet. Add it with
479 /// Element::appendChild() or another insertion method.
480 ///
481 /// @param data The comment's text.
482 ///
483 /// @return Returns the new comment node.
484 ///
485 Node createComment(std::string_view data) const {
486 return Node::Adopt(
487 ulDOMDocumentCreateComment(handle_, data.data() ? data.data() : "", data.size()));
488 }
489
490 ///
491 /// Create an empty document fragment (createDocumentFragment).
492 ///
493 /// Use a fragment to build a group of nodes off the page and insert them in one step (see
494 /// DocumentFragment).
495 ///
496 /// @return Returns the new fragment.
497 ///
499 return DocumentFragment::Adopt(ulDOMDocumentCreateFragment(handle_));
500 }
501
502 ///
503 /// Create a range (createRange).
504 ///
505 /// The range starts collapsed at the start of the document. Move it with Range::setStart() or
506 /// Range::selectNodeContents().
507 ///
508 /// @return Returns the new range.
509 ///
510 Range createRange() const { return Range::Adopt(ulDOMDocumentCreateRange(handle_)); }
511
512 // --- Events -------------------------------------------------------------------------------
513
514 ///
515 /// Dispatch a synthetic event to the document (dispatchEvent).
516 ///
517 /// Listeners run before this returns. A bubbling event also reaches window listeners.
518 ///
519 /// @param type The event type (eg, `my-app-ready`).
520 ///
521 /// @param init Whether the event bubbles, can be canceled, and is composed (see EventInit).
522 ///
523 /// @return Returns false if a listener canceled the event with Event::preventDefault() (only
524 /// possible when `init.cancelable` is true). Returns true otherwise.
525 ///
526 bool dispatchEvent(std::string_view type, const EventInit& init = {}) const {
527 detail::CString t(type);
528 return ulDOMDocumentDispatchEvent(handle_, t.c_str(), detail::EventInitFlags(init),
529 nullptr);
530 }
531
532 ///
533 /// Dispatch a synthetic CustomEvent with a string payload to the document.
534 ///
535 /// Listeners run before this returns. Native listeners read the payload with
536 /// CustomEvent::detail(), and page scripts read it as `event.detail`.
537 ///
538 /// @param type The event type.
539 ///
540 /// @param event_detail The payload as UTF-8 text (see Element::dispatchCustomEvent()).
541 ///
542 /// @param init Whether the event bubbles, can be canceled, and is composed (see
543 /// EventInit).
544 ///
545 /// @return Returns false if a listener canceled the event with Event::preventDefault(). Returns
546 /// true otherwise.
547 ///
548 bool dispatchCustomEvent(std::string_view type, std::string_view event_detail,
549 const EventInit& init = {}) const {
550 detail::CString t(type);
551 detail::CString d(event_detail);
552 return ulDOMDocumentDispatchCustomEvent(handle_, t.c_str(),
553 detail::EventInitFlags(init), d.c_str(),
554 nullptr);
555 }
556
557 ///
558 /// Listen for an event on the document (addEventListener).
559 ///
560 /// This works like Element::addEventListener().
561 ///
562 /// @param type The event type (eg, `keydown`).
563 ///
564 /// @param callback The function to call for each event. It can take the event or nothing:
565 /// `[](dom::Event event) { ... }` or `[] { ... }`.
566 ///
567 /// @param options The listener's options (see AddEventListenerOptions).
568 ///
569 /// @return Returns a handle for removing the listener, which you can ignore. It's empty when
570 /// nothing was added (the page is gone or `options.signal` is already aborted).
571 ///
572 template <typename F>
573 EventListener addEventListener(std::string_view type, F&& callback,
574 const AddEventListenerOptions& options = {}) const {
575 using Fn = std::decay_t<F>;
576 static_assert(detail::InvocableWithEvent<Fn> || std::is_invocable_v<Fn&>,
577 "addEventListener takes a callable invocable with (dom::Event) or ()");
578 detail::CString t(type);
579 ULDOMEventCallback thunk = &detail::EventThunk<Fn>;
580 ULUserDataDestroyCallback destroy = &detail::DeleteCallable<Fn>;
581 return detail::AddListener<Fn>(std::forward<F>(callback), options,
582 [&](unsigned flags, void* fn) {
583 return ulDOMDocumentAddEventListener(handle_, t.c_str(),
584 flags, thunk, fn,
585 destroy);
586 });
587 }
588
589 ///
590 /// Listen for an event on the document, with options as flags (eg, `dom::Once | dom::Capture`).
591 ///
592 /// @param type The event type.
593 ///
594 /// @param callback The function to call for each event. It can take the event or nothing.
595 ///
596 /// @param flags The listener's options (see EventListenerFlags).
597 ///
598 /// @return Returns a handle for removing the listener, which you can ignore (empty when the
599 /// page is gone).
600 ///
601 template <typename F>
602 EventListener addEventListener(std::string_view type, F&& callback, EventListenerFlags flags) const {
603 return addEventListener(type, std::forward<F>(callback), detail::ToOptions(flags));
604 }
605
606 ///
607 /// Listen for an event on the document by calling a member function of an object.
608 ///
609 /// @param type The event type.
610 ///
611 /// @param receiver The object to call.
612 ///
613 /// @param method The member function to call. It can take the event or nothing.
614 ///
615 /// @param options The listener's options (see AddEventListenerOptions).
616 ///
617 /// @return Returns a handle for removing the listener, which you can ignore. It's empty when
618 /// nothing was added (the page is gone or `options.signal` is already aborted).
619 ///
620 /// @warning The listener calls `receiver` for as long as it lives, so `receiver` must outlive
621 /// the page, or the listener must be added with the signal of an AbortController
622 /// that `receiver` owns. The smart-pointer overload with a std::weak_ptr skips calls
623 /// once the object is gone instead.
624 ///
625 template <typename C, typename M>
626 requires(std::is_class_v<C> && std::is_member_function_pointer_v<M>
628 EventListener addEventListener(std::string_view type, C& receiver, M method,
629 const AddEventListenerOptions& options = {}) const {
630 return addEventListener(type, detail::WrapBorrowedMember(receiver, method), options);
631 }
632
633 ///
634 /// Listen for an event on the document by calling a member function of an object, with
635 /// options as flags.
636 ///
637 /// @param type The event type.
638 ///
639 /// @param receiver The object to call.
640 ///
641 /// @param method The member function to call. It can take the event or nothing.
642 ///
643 /// @param flags The listener's options (see EventListenerFlags).
644 ///
645 /// @return Returns a handle for removing the listener, which you can ignore (empty when the
646 /// page is gone).
647 ///
648 /// @warning The listener calls `receiver` for as long as it lives, so `receiver` must outlive
649 /// the page. To tie the listener to `receiver` instead, use the options overload
650 /// with the signal of an AbortController that `receiver` owns.
651 ///
652 template <typename C, typename M>
653 requires(std::is_class_v<C> && std::is_member_function_pointer_v<M>
654 && !LockableHolder<C>)
655 EventListener addEventListener(std::string_view type, C& receiver, M method,
656 EventListenerFlags flags) const {
657 return addEventListener(type, detail::WrapBorrowedMember(receiver, method), flags);
658 }
659
660 ///
661 /// Listen for an event on the document by calling a member function through a smart pointer.
662 ///
663 /// A std::shared_ptr keeps the object alive for as long as the listener exists. A std::weak_ptr
664 /// doesn't, and events are skipped once the object is gone.
665 ///
666 /// @param type The event type.
667 ///
668 /// @param holder The smart pointer (a std::shared_ptr, a std::weak_ptr, or any type with a
669 /// HolderTraits specialization).
670 ///
671 /// @param method The member function to call. It can take the event or nothing.
672 ///
673 /// @param options The listener's options (see AddEventListenerOptions).
674 ///
675 /// @return Returns a handle for removing the listener, which you can ignore. It's empty when
676 /// nothing was added (the page is gone or `options.signal` is already aborted).
677 ///
678 template <typename H, typename M>
679 requires(LockableHolder<H> && std::is_member_function_pointer_v<M>)
680 EventListener addEventListener(std::string_view type, H holder, M method,
681 const AddEventListenerOptions& options = {}) const {
682 return addEventListener(type, detail::WrapHolderMember(std::move(holder), method), options);
683 }
684
685 ///
686 /// Listen for an event on the document by calling a member function through a smart pointer,
687 /// with options as flags.
688 ///
689 /// @param type The event type.
690 ///
691 /// @param holder The smart pointer (a std::shared_ptr, a std::weak_ptr, or any type with a
692 /// HolderTraits specialization).
693 ///
694 /// @param method The member function to call. It can take the event or nothing.
695 ///
696 /// @param flags The listener's options (see EventListenerFlags).
697 ///
698 /// @return Returns a handle for removing the listener, which you can ignore (empty when the
699 /// page is gone).
700 ///
701 template <typename H, typename M>
702 requires(LockableHolder<H> && std::is_member_function_pointer_v<M>)
703 EventListener addEventListener(std::string_view type, H holder, M method,
704 EventListenerFlags flags) const {
705 return addEventListener(type, detail::WrapHolderMember(std::move(holder), method), flags);
706 }
707
708 ///
709 /// Listen for an event on every element that matches a CSS selector, including elements added
710 /// later (event delegation from the document).
711 ///
712 /// Works like Element::On().
713 ///
714 /// @param selector A CSS selector (eg, `.row button`). The callback runs when the event's
715 /// target or one of its ancestors matches it. A malformed selector adds
716 /// nothing and logs a warning.
717 ///
718 /// @param type The event type.
719 ///
720 /// @param callback The function to call for each matching event. It can take the event and
721 /// the matching element, only the matching element, or nothing.
722 ///
723 /// @param options The listener's options (see AddEventListenerOptions).
724 ///
725 /// @return Returns a handle for removing the listener, which you can ignore. It's empty when
726 /// nothing was added (the page is gone or `options.signal` is already aborted).
727 ///
728 template <typename F>
729 EventListener On(std::string_view selector, std::string_view type, F&& callback,
730 const AddEventListenerOptions& options = {}) const {
731 using Fn = std::decay_t<F>;
732 static_assert(detail::InvocableWithEvent<Fn, Element> || std::is_invocable_v<Fn&, Element>
733 || std::is_invocable_v<Fn&>,
734 "On takes a callable invocable with (dom::Event, dom::Element), "
735 "(dom::Element), or ()");
736 detail::CString sel(selector);
737 detail::CString t(type);
738 ULDOMDelegatedEventCallback thunk = &detail::DelegatedThunk<Fn>;
739 ULUserDataDestroyCallback destroy = &detail::DeleteCallable<Fn>;
740 return detail::AddListener<Fn>(std::forward<F>(callback), options,
741 [&](unsigned flags, void* fn) {
742 return ulDOMDocumentAddDelegatedEventListener(
743 handle_, sel.c_str(), t.c_str(), flags, thunk, fn,
744 destroy);
745 });
746 }
747
748 ///
749 /// Listen for an event on every element that matches a CSS selector, with options as flags.
750 ///
751 /// @param selector A CSS selector the event's target or one of its ancestors must match.
752 ///
753 /// @param type The event type.
754 ///
755 /// @param callback The function to call for each matching event. It can take the event and
756 /// the matching element, only the matching element, or nothing.
757 ///
758 /// @param flags The listener's options (see EventListenerFlags).
759 ///
760 /// @return Returns a handle for removing the listener, which you can ignore (empty when the
761 /// page is gone).
762 ///
763 template <typename F>
764 EventListener On(std::string_view selector, std::string_view type, F&& callback,
765 EventListenerFlags flags) const {
766 return On(selector, type, std::forward<F>(callback), detail::ToOptions(flags));
767 }
768
769 ///
770 /// Same as On(), but returns a Result with the reason for a failure (eg, a SyntaxError for a
771 /// malformed selector).
772 ///
773 /// @param selector A CSS selector the event's target or one of its ancestors must match.
774 ///
775 /// @param type The event type.
776 ///
777 /// @param callback The function to call for each matching event. It can take the event and
778 /// the matching element, only the matching element, or nothing.
779 ///
780 /// @param options The listener's options (see AddEventListenerOptions).
781 ///
782 /// @return Returns a handle for removing the listener (empty when `options.signal` is
783 /// already aborted). Fails with a SyntaxError if `selector` is malformed.
784 ///
785 template <typename F>
786 [[nodiscard]] Result<EventListener> On(std::string_view selector, std::string_view type,
787 F&& callback, const AddEventListenerOptions& options,
788 Checked_t) const {
789 if (IsEmpty())
790 return detail::EmptyHandleError();
791 return detail::CheckedDelegation(On(selector, type, std::forward<F>(callback), options),
792 IsAlive(), options, selector);
793 }
794
795 ///
796 /// Same as On() with default options, but returns a Result with the reason for a failure.
797 ///
798 /// @param selector A CSS selector the event's target or one of its ancestors must match.
799 ///
800 /// @param type The event type.
801 ///
802 /// @param callback The function to call for each matching event. It can take the event and
803 /// the matching element, only the matching element, or nothing.
804 ///
805 /// @return Returns a handle for removing the listener. Fails with a SyntaxError if
806 /// `selector` is malformed.
807 ///
808 template <typename F>
809 [[nodiscard]] Result<EventListener> On(std::string_view selector, std::string_view type,
810 F&& callback, Checked_t) const {
811 return On(selector, type, std::forward<F>(callback), AddEventListenerOptions {}, Checked);
812 }
813
814 ///
815 /// Listen for an event on every element that matches a CSS selector by calling a member
816 /// function of an object.
817 ///
818 /// @param selector A CSS selector the event's target or one of its ancestors must match.
819 ///
820 /// @param type The event type.
821 ///
822 /// @param receiver The object to call.
823 ///
824 /// @param method The member function to call. It can take the event and the matching
825 /// element, only the matching element, or nothing.
826 ///
827 /// @param options The listener's options (see AddEventListenerOptions).
828 ///
829 /// @return Returns a handle for removing the listener, which you can ignore. It's empty when
830 /// nothing was added (the page is gone or `options.signal` is already aborted).
831 ///
832 /// @warning The listener calls `receiver` for as long as it lives, so `receiver` must outlive
833 /// the page, or the listener must be added with the signal of an AbortController
834 /// that `receiver` owns. The smart-pointer overload with a std::weak_ptr skips calls
835 /// once the object is gone instead.
836 ///
837 template <typename C, typename M>
838 requires(std::is_class_v<C> && std::is_member_function_pointer_v<M>
840 EventListener On(std::string_view selector, std::string_view type, C& receiver, M method,
841 const AddEventListenerOptions& options = {}) const {
842 return On(selector, type, detail::WrapBorrowedMember(receiver, method), options);
843 }
844
845 ///
846 /// Listen for an event on every element that matches a CSS selector by calling a member
847 /// function of an object, with options as flags.
848 ///
849 /// @param selector A CSS selector the event's target or one of its ancestors must match.
850 ///
851 /// @param type The event type.
852 ///
853 /// @param receiver The object to call.
854 ///
855 /// @param method The member function to call. It can take the event and the matching
856 /// element, only the matching element, or nothing.
857 ///
858 /// @param flags The listener's options (see EventListenerFlags).
859 ///
860 /// @return Returns a handle for removing the listener, which you can ignore (empty when the
861 /// page is gone).
862 ///
863 /// @warning The listener calls `receiver` for as long as it lives, so `receiver` must outlive
864 /// the page. To tie the listener to `receiver` instead, use the options overload
865 /// with the signal of an AbortController that `receiver` owns.
866 ///
867 template <typename C, typename M>
868 requires(std::is_class_v<C> && std::is_member_function_pointer_v<M>
869 && !LockableHolder<C>)
870 EventListener On(std::string_view selector, std::string_view type, C& receiver, M method,
871 EventListenerFlags flags) const {
872 return On(selector, type, detail::WrapBorrowedMember(receiver, method), flags);
873 }
874
875 ///
876 /// Listen for an event on every element that matches a CSS selector by calling a member
877 /// function through a smart pointer.
878 ///
879 /// A std::shared_ptr keeps the object alive for as long as the listener exists. A std::weak_ptr
880 /// doesn't, and events are skipped once the object is gone.
881 ///
882 /// @param selector A CSS selector the event's target or one of its ancestors must match.
883 ///
884 /// @param type The event type.
885 ///
886 /// @param holder The smart pointer (a std::shared_ptr, a std::weak_ptr, or any type with a
887 /// HolderTraits specialization).
888 ///
889 /// @param method The member function to call. It can take the event and the matching
890 /// element, only the matching element, or nothing.
891 ///
892 /// @param options The listener's options (see AddEventListenerOptions).
893 ///
894 /// @return Returns a handle for removing the listener, which you can ignore. It's empty when
895 /// nothing was added (the page is gone or `options.signal` is already aborted).
896 ///
897 template <typename H, typename M>
898 requires(LockableHolder<H> && std::is_member_function_pointer_v<M>)
899 EventListener On(std::string_view selector, std::string_view type, H holder, M method,
900 const AddEventListenerOptions& options = {}) const {
901 return On(selector, type, detail::WrapHolderMember(std::move(holder), method), options);
902 }
903
904 ///
905 /// Listen for an event on every element that matches a CSS selector by calling a member
906 /// function through a smart pointer, with options as flags.
907 ///
908 /// @param selector A CSS selector the event's target or one of its ancestors must match.
909 ///
910 /// @param type The event type.
911 ///
912 /// @param holder The smart pointer (a std::shared_ptr, a std::weak_ptr, or any type with a
913 /// HolderTraits specialization).
914 ///
915 /// @param method The member function to call. It can take the event and the matching
916 /// element, only the matching element, or nothing.
917 ///
918 /// @param flags The listener's options (see EventListenerFlags).
919 ///
920 /// @return Returns a handle for removing the listener, which you can ignore (empty when the
921 /// page is gone).
922 ///
923 template <typename H, typename M>
924 requires(LockableHolder<H> && std::is_member_function_pointer_v<M>)
925 EventListener On(std::string_view selector, std::string_view type, H holder, M method,
926 EventListenerFlags flags) const {
927 return On(selector, type, detail::WrapHolderMember(std::move(holder), method), flags);
928 }
929
930 // --- Interop with the C API (most embedders never touch raw handles) -----------------------
931
932 ///
933 /// Wrap a C handle you own, taking ownership of it.
934 ///
935 /// @param handle A handle from the C API that you would otherwise destroy with
936 /// ulDestroyDOMDocument() (NULL gives an empty Document).
937 ///
938 /// @return Returns a Document that destroys `handle` when it's done.
939 ///
940 static Document Adopt(ULDOMDocument handle) { return Document(handle); }
941
942 ///
943 /// Wrap a C handle the library owns (eg, a callback argument), adding a reference.
944 ///
945 /// @param handle The borrowed handle (NULL gives an empty Document).
946 ///
947 /// @return Returns a Document with its own reference, so you can keep it after the callback.
948 ///
950 return Document(handle ? ulCreateDOMDocumentRef(handle) : nullptr);
951 }
952
953 ///
954 /// Get the C handle, for passing to the `<Ultralight/CAPI/CAPI_DOMDocument.h>` functions.
955 ///
956 /// @return Returns the handle (NULL for an empty Document). This Document still owns it, so
957 /// don't destroy it.
958 ///
959 ULDOMDocument raw() const { return handle_; }
960
961 ///
962 /// Give up ownership of the C handle and return it. This Document becomes empty.
963 ///
964 /// @return Returns the handle. You must call ulDestroyDOMDocument() when finished.
965 ///
967 ULDOMDocument handle = handle_;
968 handle_ = nullptr;
969 return handle;
970 }
971
972 protected:
973 explicit Document(ULDOMDocument handle) : handle_(handle) {}
974
975 private:
976 ULDOMDocument handle_ = nullptr;
977};
978
979/// \cond INTERNAL
980namespace detail {
981
982// Document mixes public proxies with its private handle, so it isn't standard-layout and
983// offsetof on it is conditionally supported (the same arrangement as Element; see
984// PropertiesImpl.h).
985#if defined(__GNUC__)
986#pragma GCC diagnostic push
987#pragma GCC diagnostic ignored "-Winvalid-offsetof"
988#endif
989
990inline ULDOMDocument OwnerDocumentHandle(const void* member, size_t member_offset) {
991 const Document* document = reinterpret_cast<const Document*>(
992 reinterpret_cast<const char*>(member) - member_offset);
993 return document->raw();
994}
995
996template <>
997inline size_t DocumentPropOffset<DocumentTitleTag>() {
998 return offsetof(Document, title);
999}
1000
1001#if defined(__GNUC__)
1002#pragma GCC diagnostic pop
1003#endif
1004
1005template <class Tag>
1006void DocumentStringProp<Tag>::operator=(std::string_view value) const {
1007 Tag::Set(OwnerDocumentHandle(this, DocumentPropOffset<Tag>()), value);
1008}
1009
1010template <class Tag>
1011void DocumentStringProp<Tag>::operator+=(std::string_view suffix) const {
1012 ULDOMDocument h = OwnerDocumentHandle(this, DocumentPropOffset<Tag>());
1013 std::string combined = Tag::Get(h);
1014 combined.append(suffix);
1015 Tag::Set(h, combined);
1016}
1017
1018template <class Tag>
1019DocumentStringProp<Tag>::operator std::string() const {
1020 return Tag::Get(OwnerDocumentHandle(this, DocumentPropOffset<Tag>()));
1021}
1022
1023} // namespace detail
1024/// \endcond
1025
1027 return Document::Adopt(ulDOMElementGetDocument(raw()));
1028}
1029
1031 return Document::Adopt(ulDOMNodeGetOwnerDocument(detail_.handle));
1032}
1033
1035 return Document::Adopt(ulDOMElementGetContentDocument(raw()));
1036}
1037
1038} // namespace dom
1039} // namespace ultralight
struct C_DOMDocument * ULDOMDocument
Opaque handle to a page's DOM document.
Definition View.h:39
Web-page container rendered to an offscreen surface.
Definition View.h:483
A container for assembling DOM nodes off the page.
Definition DocumentFragment.h:43
static DocumentFragment Adopt(ULDOMFragment handle)
Wrap a C handle you own, taking ownership of it.
Definition DocumentFragment.h:229
The root of a page's DOM tree in a View or frame.
Definition Document.h:106
~Document()
Destroy this handle (the document itself isn't affected).
Definition Document.h:162
EventListener addEventListener(std::string_view type, F &&callback, EventListenerFlags flags) const
Listen for an event on the document, with options as flags (eg, dom::Once | dom::Capture).
Definition Document.h:602
Element body() const
Get the document's body element (body).
Definition Document.h:320
Element elementFromPoint(double x, double y) const
Find the topmost element at a point in the viewport (elementFromPoint).
Definition Document.h:279
Element head() const
Get the document's head element (head).
Definition Document.h:327
EventListener addEventListener(std::string_view type, C &receiver, M method, EventListenerFlags flags) const
Listen for an event on the document by calling a member function of an object, with options as flags.
Definition Document.h:655
EventListener addEventListener(std::string_view type, H holder, M method, const AddEventListenerOptions &options={}) const
Listen for an event on the document by calling a member function through a smart pointer.
Definition Document.h:680
Element firstElementChild() const
Get the document's first child element (firstElementChild).
Definition Document.h:369
Result< EventListener > On(std::string_view selector, std::string_view type, F &&callback, Checked_t) const
Same as On() with default options, but returns a Result with the reason for a failure.
Definition Document.h:809
EventListener On(std::string_view selector, std::string_view type, C &receiver, M method, EventListenerFlags flags) const
Listen for an event on every element that matches a CSS selector by calling a member function of an o...
Definition Document.h:870
Node lastChild() const
Get the document's last child of any kind (lastChild).
Definition Document.h:362
static Document Adopt(ULDOMDocument handle)
Wrap a C handle you own, taking ownership of it.
Definition Document.h:940
static Document FromBorrowed(ULDOMDocument handle)
Wrap a C handle the library owns (eg, a callback argument), adding a reference.
Definition Document.h:949
ULDOMDocument LeakRef()
Give up ownership of the C handle and return it.
Definition Document.h:966
EventListener addEventListener(std::string_view type, F &&callback, const AddEventListenerOptions &options={}) const
Listen for an event on the document (addEventListener).
Definition Document.h:573
NodeList childNodes() const
Get the document's children of every kind in order (childNodes).
Definition Document.h:410
Element querySelector(std::string_view selectors) const
Find the first element matching a CSS selector (querySelector).
Definition Document.h:203
Document(Document &&other) noexcept
Move constructor (other becomes empty).
Definition Document.h:145
EventListener On(std::string_view selector, std::string_view type, H holder, M method, EventListenerFlags flags) const
Listen for an event on every element that matches a CSS selector by calling a member function through...
Definition Document.h:925
Window defaultView() const
Get the document's window (defaultView).
Definition Window.h:480
Selection getSelection() const
Get the document's selection (getSelection).
Definition Selection.h:551
EventListener On(std::string_view selector, std::string_view type, H holder, M method, const AddEventListenerOptions &options={}) const
Listen for an event on every element that matches a CSS selector by calling a member function through...
Definition Document.h:899
bool hasFocus() const
Whether or not the document has keyboard focus (hasFocus).
Definition Document.h:345
DocumentFragment createDocumentFragment() const
Create an empty document fragment (createDocumentFragment).
Definition Document.h:498
Document()=default
Create an empty Document.
Element documentElement() const
Get the document's root element (documentElement).
Definition Document.h:311
Result< EventListener > On(std::string_view selector, std::string_view type, F &&callback, const AddEventListenerOptions &options, Checked_t) const
Same as On(), but returns a Result with the reason for a failure (eg, a SyntaxError for a malformed s...
Definition Document.h:786
EventListener On(std::string_view selector, std::string_view type, F &&callback, EventListenerFlags flags) const
Listen for an event on every element that matches a CSS selector, with options as flags.
Definition Document.h:764
detail::DocumentStringProp< detail::DocumentTitleTag > title
The document's title (title), the text of its <title> element with whitespace collapsed.
Definition Document.h:114
Node createComment(std::string_view data) const
Create a comment node (createComment).
Definition Document.h:485
size_t childElementCount() const
Get the number of the document's child elements (childElementCount).
Definition Document.h:387
Document(View *view)
Get the document of a View's main frame.
Definition Document.h:130
bool IsEmpty() const
Whether or not this Document is empty (it holds no handle).
Definition Document.h:172
bool IsAlive() const
Whether or not this Document is valid (it isn't empty and its page is still alive).
Definition Document.h:179
Range createRange() const
Create a range (createRange).
Definition Document.h:510
bool dispatchEvent(std::string_view type, const EventInit &init={}) const
Dispatch a synthetic event to the document (dispatchEvent).
Definition Document.h:526
ElementList querySelectorAll(std::string_view selectors) const
Find every element matching a CSS selector (querySelectorAll).
Definition Document.h:240
Element lastElementChild() const
Get the document's last child element (lastElementChild).
Definition Document.h:378
Element activeElement() const
Get the element that has keyboard focus (activeElement).
Definition Document.h:335
EventListener addEventListener(std::string_view type, H holder, M method, EventListenerFlags flags) const
Listen for an event on the document by calling a member function through a smart pointer,...
Definition Document.h:703
Document(ULDOMDocument handle)
Definition Document.h:973
ElementList elementsFromPoint(double x, double y) const
Find every element at a point in the viewport, topmost first (elementsFromPoint).
Definition Document.h:299
EventListener On(std::string_view selector, std::string_view type, C &receiver, M method, const AddEventListenerOptions &options={}) const
Listen for an event on every element that matches a CSS selector by calling a member function of an o...
Definition Document.h:840
Node createTextNode(std::string_view data) const
Create a text node (createTextNode).
Definition Document.h:470
bool dispatchCustomEvent(std::string_view type, std::string_view event_detail, const EventInit &init={}) const
Dispatch a synthetic CustomEvent with a string payload to the document.
Definition Document.h:548
Node firstChild() const
Get the document's first child of any kind (firstChild).
Definition Document.h:355
Document & operator=(Document other) noexcept
Assignment (copies or moves).
Definition Document.h:154
ULDOMDocument raw() const
Get the C handle, for passing to the <Ultralight/CAPI/CAPI_DOMDocument.h> functions.
Definition Document.h:959
bool hasChildNodes() const
Whether or not the document has any children (hasChildNodes).
Definition Document.h:417
ElementList children() const
Get the document's child elements in order (children).
Definition Document.h:397
Element getElementById(std::string_view id) const
Find the element with a certain id (getElementById).
Definition Document.h:190
EventListener addEventListener(std::string_view type, C &receiver, M method, const AddEventListenerOptions &options={}) const
Listen for an event on the document by calling a member function of an object.
Definition Document.h:628
Result< ElementList > querySelectorAll(std::string_view selectors, Checked_t) const
Same as querySelectorAll(), but returns a Result with the reason for a failure (eg,...
Definition Document.h:252
EventListener On(std::string_view selector, std::string_view type, F &&callback, const AddEventListenerOptions &options={}) const
Listen for an event on every element that matches a CSS selector, including elements added later (eve...
Definition Document.h:729
Document(const Document &other)
Copy constructor (both handles refer to the same document).
Definition Document.h:137
Result< Element > querySelector(std::string_view selectors, Checked_t) const
Same as querySelector(), but returns a Result with the reason for a failure (eg, a SyntaxError for a ...
Definition Document.h:215
Element createElement(std::string_view tag_name) const
Create an element (createElement).
Definition Document.h:455
A handle to an element on a page.
Definition Element.h:142
Document ownerDocument() const
Get the document this element belongs to (ownerDocument).
Definition Document.h:1026
static Element Adopt(ULDOMElement handle)
Wrap a C handle you own, taking ownership of it.
Definition Element.h:1987
Element()
Create an empty Element.
Definition Element.h:368
ULDOMElement raw() const
Get the C handle, for passing to the <Ultralight/CAPI/CAPI_DOMElement.h> functions.
Definition Element.h:2006
A fixed list of DOM elements returned by a query or an Element::children() call.
Definition ElementList.h:55
static ElementList Adopt(ULDOMElementList handle)
Wrap a C handle you own, taking ownership of it.
Definition ElementList.h:168
static Error PageGone()
Create a page-gone error (is_page_gone() returns true).
Definition Error.h:123
A handle to a registered DOM event listener.
Definition EventListener.h:151
Document contentDocument() const
Get the document inside the frame (contentDocument).
Definition Document.h:1034
A reference to an item in the DOM tree.
Definition Node.h:147
detail::NodeStorage detail_
Internal storage (not part of the API).
Definition Node.h:166
Document ownerDocument() const
Get the document this node belongs to (ownerDocument).
Definition Document.h:1030
static Node Adopt(ULDOMNode handle)
Wrap a C handle you own, taking ownership of it.
Definition Node.h:520
A fixed list of nodes of any kind (eg, the results of Node::childNodes()).
Definition NodeList.h:34
static NodeList Adopt(ULDOMNodeList handle)
Wrap a C handle you own, taking ownership of it.
Definition NodeList.h:143
A handle to a live range (a span of a page between two boundary points).
Definition Range.h:58
static Range Adopt(ULDOMRange handle)
Wrap a C handle you own, taking ownership of it.
Definition Range.h:525
A handle to the active user selection or caret in a document.
Definition Selection.h:101
The viewport of a DOM document.
Definition Window.h:73
Whether or not the DOM API can hold an object through H (ignoring const and references),...
Definition Holders.h:41
Data-binding API that connects native C++ data to HTML and CSS markup.
Definition ActionInfo.h:13
Direct C++ access to modify page elements and handle events.
constexpr Checked_t Checked
Pass to a DOM call to get a dom::Result holding the reason for a failure instead of an empty value.
Definition Error.h:299
EventListenerFlags
Options for adding an event listener, as flags (addEventListener() and On()).
Definition EventListener.h:41
Expected< T, Error > Result
The result of a DOM operation that can fail (either a T or a dom::Error).
Definition Error.h:277
Root namespace for every public Ultralight type, function, and enumeration.
Options for adding an event listener, like the web's options object for addEventListener().
Definition EventListener.h:526
The type of dom::Checked.
Definition Error.h:282
Options for an event you dispatch yourself (the web's EventInit).
Definition Event.h:30