docs
Loading...
Searching...
No Matches
Error.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// X11's headers define None as a macro, which would break the None enumerators.
7#pragma push_macro("None")
8#undef None
9
10#include <Ultralight/CAPI/CAPI_DOMDocument.h>
11#include <Ultralight/CAPI/CAPI_String.h>
12#include <Ultralight/detail/Expected.h>
13#include <Ultralight/dom/detail/Support.h>
14
15#include <string>
16#include <type_traits>
17#include <utility>
18
19// The Expected/Unexpected implementation is shared across the typed header families and
20// lives in <Ultralight/detail/Expected.h>; the dom/ names below alias it. The reader-facing
21// story lives on the dom::Result alias doc at the end of this header.
22
23namespace ultralight {
24namespace dom {
25
26/// \cond INTERNAL
27namespace detail {
28class ErrorScope;
29} // namespace detail
30/// \endcond
31
32///
33/// The kinds of DOM exception a dom::Error can hold (the web's DOMException names, plus TypeError).
34///
35/// The values match ULDOMErrorCode in the C API. They stay the same across releases. Later
36/// versions may add types, so handle a value you don't know (eg, with a `default` case).
37///
38enum class ErrorType : unsigned {
39 None = 0, ///< Not a DOM exception (eg, a page-gone error).
40 Unknown, ///< A DOM exception with no code of its own here.
41 SyntaxError, ///< A malformed selector, or an invalid position for
42 ///< Element::insertAdjacentHTML() and its siblings.
43 InvalidCharacterError, ///< An invalid tag, attribute, or class name (only the C API
44 ///< reports it, since the typed calls that take these names
45 ///< have no dom::Checked form).
46 HierarchyRequestError, ///< The tree would contain a cycle, or the node can't go there
47 ///< (eg, text directly under the document).
48 NotFoundError, ///< A node isn't where the call needs it (eg, a reference child
49 ///< that isn't a child).
50 NoModificationAllowedError, ///< The target can't be changed (eg, it has no parent).
51 InvalidStateError, ///< The call isn't valid in the target's current state.
52 IndexSizeError, ///< An index or offset is out of range.
53 NotSupportedError, ///< The operation isn't supported.
54 TypeError, ///< An argument has the wrong type (eg, a submitter passed to
55 ///< HTMLFormElement::requestSubmit() that isn't a submit
56 ///< button).
57 InvalidNodeTypeError, ///< The node is the wrong kind for the call (eg, selecting a
58 ///< node that has no parent).
59 WrongDocumentError, ///< A node or range belongs to a different document.
60};
61
62///
63/// The reason a DOM operation failed.
64///
65/// dom::Error holds the details of a failed DOM operation, such as a DOM exception or a handle
66/// whose page is gone.
67///
68/// Passing dom::Checked returns a dom::Result you can inspect on failure:
69///
70/// ```
71/// dom::Result<dom::Element> total =
72/// document.querySelector("#scores td.total", dom::Checked);
73/// if (!total) {
74/// if (total.error().is_page_gone())
75/// return; // the page went away (nothing to report)
76/// Log(total.error().message()); // eg, a malformed selector
77/// }
78/// ```
79///
80/// ## Getting an Error
81///
82/// DOM operations return an empty value on failure instead of throwing C++ exceptions. Most code
83/// never needs dom::Error because operations on empty values fail safely without extra checks.
84///
85/// To inspect a failure, pass dom::Checked as an extra argument to receive a dom::Result holding
86/// either the value or a dom::Error.
87///
88/// When you've inspected the error and want to continue with an empty value, pass the dom::Result
89/// to dom::OrEmpty().
90///
91/// ## Kinds of Errors
92///
93/// Specific query methods identify each failure condition:
94///
95/// | Kind | How to Check |
96/// |---------------|-------------------------------------------------------------|
97/// | DOM exception | is_dom_exception() is true, and type() gives the code |
98/// | Empty handle | is_empty() is true |
99/// | Page gone | is_page_gone() is true |
100/// | Native error | Created by Make() or the library, other checks return false |
101///
102/// How you handle an error depends on its condition:
103///
104/// - **Ignore page-gone errors in routine code.** A page can navigate away at any moment, leaving
105/// nothing to report.
106/// - **Treat empty-handle errors as application bugs.** They indicate that native code operated on
107/// an unassigned handle, a moved-from handle, or a lookup that found nothing.
108/// - **Inspect type() rather than message() in code.** The message text is meant for human
109/// diagnostics and can change between releases, while type() returns stable dom::ErrorType
110/// values.
111///
112/// @note An error you get through dom::Checked isn't logged by DOM diagnostics.
113///
114/// @see dom::Checked, dom::Result, dom::ErrorType, dom::OrEmpty()
115///
116class Error {
117 public:
118 ///
119 /// Create a page-gone error (is_page_gone() returns true).
120 ///
121 /// @return Returns the new Error.
122 ///
123 static Error PageGone() { return Error(Kind::kPageGone); }
124
125 ///
126 /// Create an error from a DOM exception reported by the C API.
127 ///
128 /// @param error The ULDOMError a C function filled in. The new Error takes its message, and
129 /// `error` is reset to zero (so don't destroy the message yourself).
130 ///
131 /// @return Returns the new Error (is_dom_exception() returns true).
132 ///
133 static Error AdoptDOMException(ULDOMError& error) {
134 Error result(Kind::kDOMException);
135 result.type_ = static_cast<ErrorType>(error.code);
136 result.message_ = detail::TakeString(error.message);
137 error.code = kULDOMErrorCode_None;
138 error.message = nullptr;
139 return result;
140 }
141
142 ///
143 /// Create a native error.
144 ///
145 /// @param type The error type, returned by type().
146 ///
147 /// @param message The description, returned by message().
148 ///
149 /// @return Returns the new Error.
150 ///
151 static Error Make(ErrorType type, std::string message) {
152 Error result(Kind::kNative);
153 result.type_ = type;
154 result.message_ = std::move(message);
155 return result;
156 }
157
158 ///
159 /// Whether or not this error means the handle's page is gone.
160 ///
161 bool is_page_gone() const { return kind_ == Kind::kPageGone; }
162
163 ///
164 /// Whether or not this error means a handle holds nothing (see the class notes).
165 ///
166 bool is_empty() const { return kind_ == Kind::kEmpty; }
167
168 ///
169 /// Whether or not this error holds a DOM exception.
170 ///
171 bool is_dom_exception() const { return kind_ == Kind::kDOMException; }
172
173 ///
174 /// Get the error's type.
175 ///
176 /// @return Returns the kind of DOM exception, the type passed to Make(), or ErrorType::None
177 /// for a page-gone or empty-handle error.
178 ///
179 ErrorType type() const { return type_; }
180
181 ///
182 /// Get a readable description of the error.
183 ///
184 /// A DOM exception without a message of its own reads as its type (eg, `SyntaxError`). The text
185 /// is written for people and can change in any release, so check type() in code instead.
186 ///
187 std::string message() const {
188 if (kind_ == Kind::kPageGone)
189 return "The page behind this handle is gone (it navigated away, its frame was removed, or "
190 "its View was destroyed).";
191 if (kind_ == Kind::kEmpty)
192 return "A handle used in this call is empty (it was never assigned, was moved from, or "
193 "came from a lookup that found nothing).";
194 if (message_.empty() && kind_ == Kind::kDOMException)
195 return TypeName(type_);
196 return message_;
197 }
198
199 private:
200 friend class detail::ErrorScope;
201
202 enum class Kind { kNative, kDOMException, kPageGone, kEmpty };
203
204 explicit Error(Kind kind) : kind_(kind) {}
205
206 static const char* TypeName(ErrorType type) {
207 switch (type) {
208 case ErrorType::SyntaxError: return "SyntaxError";
209 case ErrorType::InvalidCharacterError: return "InvalidCharacterError";
210 case ErrorType::HierarchyRequestError: return "HierarchyRequestError";
211 case ErrorType::NotFoundError: return "NotFoundError";
212 case ErrorType::NoModificationAllowedError: return "NoModificationAllowedError";
213 case ErrorType::InvalidStateError: return "InvalidStateError";
214 case ErrorType::IndexSizeError: return "IndexSizeError";
215 case ErrorType::NotSupportedError: return "NotSupportedError";
216 case ErrorType::TypeError: return "TypeError";
217 case ErrorType::InvalidNodeTypeError: return "InvalidNodeTypeError";
218 case ErrorType::WrongDocumentError: return "WrongDocumentError";
219 default: return "A DOM exception occurred.";
220 }
221 }
222
223 Kind kind_ = Kind::kNative;
225 std::string message_;
226};
227
228static_assert(static_cast<unsigned>(ErrorType::None) == kULDOMErrorCode_None
229 && static_cast<unsigned>(ErrorType::Unknown) == kULDOMErrorCode_Unknown
230 && static_cast<unsigned>(ErrorType::SyntaxError) == kULDOMErrorCode_SyntaxError
231 && static_cast<unsigned>(ErrorType::InvalidCharacterError)
232 == kULDOMErrorCode_InvalidCharacterError
233 && static_cast<unsigned>(ErrorType::HierarchyRequestError)
234 == kULDOMErrorCode_HierarchyRequestError
235 && static_cast<unsigned>(ErrorType::NotFoundError)
236 == kULDOMErrorCode_NotFoundError
237 && static_cast<unsigned>(ErrorType::NoModificationAllowedError)
238 == kULDOMErrorCode_NoModificationAllowedError
239 && static_cast<unsigned>(ErrorType::InvalidStateError)
240 == kULDOMErrorCode_InvalidStateError
241 && static_cast<unsigned>(ErrorType::IndexSizeError)
242 == kULDOMErrorCode_IndexSizeError
243 && static_cast<unsigned>(ErrorType::NotSupportedError)
244 == kULDOMErrorCode_NotSupportedError
245 && static_cast<unsigned>(ErrorType::TypeError) == kULDOMErrorCode_TypeError
246 && static_cast<unsigned>(ErrorType::InvalidNodeTypeError)
247 == kULDOMErrorCode_InvalidNodeTypeError
248 && static_cast<unsigned>(ErrorType::WrongDocumentError)
249 == kULDOMErrorCode_WrongDocumentError,
250 "dom::ErrorType must mirror ULDOMErrorCode");
251
252///
253/// Wraps an error to return it through a dom::Result (like std::unexpected).
254///
255using ultralight::detail::Unexpected;
256
257///
258/// A value of type T or an error of type E (like std::expected).
259///
260/// @warning Don't call value() when has_value() is false. That's undefined behavior, since the
261/// library never throws C++ exceptions. Check first or use value_or().
262///
263template <typename T, typename E = Error>
264using Expected = ultralight::detail::Expected<T, E>;
265
266///
267/// The result of a DOM operation that can fail (either a T or a dom::Error).
268///
269/// DOM calls return one when you pass dom::Checked.
270///
271/// Result is dom::Expected<T, dom::Error>, the library's own type with the members of
272/// std::expected (has_value(), value(), error(), value_or(), and_then(), transform(), and the
273/// rest). It's the same type in every file of your program, whichever C++ standard each file is
274/// compiled with.
275///
276template <typename T>
278
279///
280/// The type of dom::Checked.
281///
282struct Checked_t {
283 ///
284 /// Create the tag (use dom::Checked instead).
285 ///
286 explicit constexpr Checked_t() = default;
287};
288
289///
290/// Pass to a DOM call to get a dom::Result holding the reason for a failure instead of an empty
291/// value.
292///
293/// ```
294/// dom::Result<dom::Element> title = doc.querySelector(".panel h1", dom::Checked);
295/// if (!title)
296/// Log(title.error().message()); // eg, a SyntaxError for a malformed selector
297/// ```
298///
299inline constexpr Checked_t Checked{};
300
301///
302/// Get a Result's value, or a default-constructed T if it holds an error.
303///
304/// Use this when you've handled the error and want to carry on with an empty value (every
305/// operation on an empty Element does nothing):
306///
307/// ```
308/// dom::Result<dom::Element> row = doc.querySelector(selector, dom::Checked);
309/// if (!row && !row.error().is_page_gone())
310/// Log(row.error().message());
311/// dom::Element cell = dom::OrEmpty(row);
312/// ```
313///
314/// @param result The Result to read.
315///
316/// @return Returns the value or a default-constructed T.
317///
318template <typename T>
319 requires std::is_default_constructible_v<T>
320[[nodiscard]] T OrEmpty(Result<T> result) {
321 return std::move(result).value_or(T());
322}
323
324/// \cond INTERNAL
325namespace detail {
326
327// Wraps the zero-initialize-then-check ULDOMError protocol of the C API: the struct is
328// written only on DOM-exception-level failure, so a nonzero code afterward reliably means
329// a DOM exception occurred. Destroys an unconsumed message.
330class ErrorScope {
331 public:
332 ErrorScope() = default;
333 ErrorScope(const ErrorScope&) = delete;
334 ErrorScope& operator=(const ErrorScope&) = delete;
335 ~ErrorScope() { ulDestroyString(error_.message); }
336
337 ULDOMError* out() { return &error_; }
338
339 bool has_exception() const { return error_.code != kULDOMErrorCode_None; }
340
341 // On DOM-exception failure, the exception; otherwise the engine-level failure.
342 Error TakeError() {
343 if (has_exception())
344 return Error::AdoptDOMException(error_);
345 return Error::PageGone();
346 }
347
348 static Error EmptyHandle() { return Error(Error::Kind::kEmpty); }
349
350 private:
351 ULDOMError error_ = {};
352};
353
354// The typed layer checks the receiver and the handle arguments a call needs before the C call,
355// since the C API reports an empty handle and a page that's gone as the same failure.
356inline ultralight::detail::Unexpected<Error> EmptyHandleError() {
357 return ultralight::detail::Unexpected<Error>(ErrorScope::EmptyHandle());
358}
359
360// Whether a ULDOMNodeOrString list holds an empty node handle (an item with neither a node nor
361// text).
362template <typename Items>
363bool HasEmptyItem(const Items& items) {
364 for (const auto& item : items) {
365 if (!item.node && !item.text)
366 return true;
367 }
368 return false;
369}
370
371} // namespace detail
372/// \endcond
373
374} // namespace dom
375} // namespace ultralight
376
377#pragma pop_macro("None")
static Error AdoptDOMException(ULDOMError &error)
Create an error from a DOM exception reported by the C API.
Definition Error.h:133
bool is_dom_exception() const
Whether or not this error holds a DOM exception.
Definition Error.h:171
static Error PageGone()
Create a page-gone error (is_page_gone() returns true).
Definition Error.h:123
bool is_empty() const
Whether or not this error means a handle holds nothing (see the class notes).
Definition Error.h:166
ErrorType type() const
Get the error's type.
Definition Error.h:179
std::string message() const
Get a readable description of the error.
Definition Error.h:187
static Error Make(ErrorType type, std::string message)
Create a native error.
Definition Error.h:151
bool is_page_gone() const
Whether or not this error means the handle's page is gone.
Definition Error.h:161
Direct C++ access to modify page elements and handle events.
ultralight::detail::Expected< T, E > Expected
A value of type T or an error of type E (like std::expected).
Definition Error.h:264
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
T OrEmpty(Result< T > result)
Get a Result's value, or a default-constructed T if it holds an error.
Definition Error.h:320
ErrorType
The kinds of DOM exception a dom::Error can hold (the web's DOMException names, plus TypeError).
Definition Error.h:38
@ HierarchyRequestError
The tree would contain a cycle, or the node can't go there (eg, text directly under the document).
Definition Error.h:46
@ TypeError
An argument has the wrong type (eg, a submitter passed to HTMLFormElement::requestSubmit() that isn't...
Definition Error.h:54
@ IndexSizeError
An index or offset is out of range.
Definition Error.h:52
@ NoModificationAllowedError
The target can't be changed (eg, it has no parent).
Definition Error.h:50
@ InvalidNodeTypeError
The node is the wrong kind for the call (eg, selecting a node that has no parent).
Definition Error.h:57
@ InvalidCharacterError
An invalid tag, attribute, or class name (only the C API reports it, since the typed calls that take ...
Definition Error.h:43
@ NotSupportedError
The operation isn't supported.
Definition Error.h:53
@ None
Not a DOM exception (eg, a page-gone error).
Definition Error.h:39
@ WrongDocumentError
A node or range belongs to a different document.
Definition Error.h:59
@ Unknown
A DOM exception with no code of its own here.
Definition Error.h:40
@ SyntaxError
A malformed selector, or an invalid position for Element::insertAdjacentHTML() and its siblings.
Definition Error.h:41
@ NotFoundError
A node isn't where the call needs it (eg, a reference child that isn't a child).
Definition Error.h:48
@ InvalidStateError
The call isn't valid in the target's current state.
Definition Error.h:51
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.
@ Error
Error icon.
Definition Dialogs.h:57
@ None
Definition Anchor.h:36
The type of dom::Checked.
Definition Error.h:282
constexpr Checked_t()=default
Create the tag (use dom::Checked instead).