docs
Loading...
Searching...
No Matches
Value.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_JSAPI.h>
11#include <Ultralight/CAPI/CAPI_JSValue.h>
12#include <Ultralight/CAPI/CAPI_String.h>
13#include <Ultralight/detail/Exceptions.h>
14#include <Ultralight/js/Error.h>
15
16#include <cstdint>
17#include <cstdio>
18#include <cstring>
19#include <optional>
20#include <span>
21#include <string>
22#include <string_view>
23#include <type_traits>
24#include <utility>
25
26namespace ultralight {
27namespace js {
28
29///
30/// Forward declaration.
31///
32/// @see <Ultralight/js/TypeTraits.h>
33///
34template <typename T, typename = void>
35struct TypeTraits;
36
37///
38/// Tag type for the JavaScript `null` value (see js::null).
39///
40struct NullType {};
41
42///
43/// The JavaScript `null` value.
44///
45/// Use it anywhere a typed value is accepted (eg, `obj["x"] = js::null` or a bound function's
46/// return value).
47///
48inline constexpr NullType null {};
49
50///
51/// Tag type for the JavaScript `undefined` value (see js::undefined).
52///
53struct UndefinedType {};
54
55///
56/// The JavaScript `undefined` value. Use it the same way as js::null.
57///
58inline constexpr UndefinedType undefined {};
59
60///
61/// Attribute flags for a property created by Value::SetProperty(). Combine them with `|`:
62///
63/// ```
64/// obj.SetProperty("id", ctx.Make(7.0),
65/// js::PropertyAttributes::ReadOnly | js::PropertyAttributes::DontDelete);
66/// ```
67///
68/// @note The flags only apply when SetProperty() creates the property. If the object (or its
69/// prototype chain) already has one with that name, SetProperty() assigns the value and
70/// ignores the flags.
71///
72enum class PropertyAttributes : unsigned {
73 None = kULJSPropertyAttributes_None, ///< No flags (a normal property).
74 ReadOnly = kULJSPropertyAttributes_ReadOnly, ///< Script can't change its value.
75 DontEnum = kULJSPropertyAttributes_DontEnum, ///< Hidden from for...in and Object.keys.
76 DontDelete = kULJSPropertyAttributes_DontDelete, ///< Script can't delete it.
77};
78
79///
80/// Combine property attribute flags.
81///
82/// @return Returns the union of both flag sets.
83///
85 return static_cast<PropertyAttributes>(static_cast<unsigned>(a) | static_cast<unsigned>(b));
86}
87
88///
89/// The type of a JavaScript value.
90///
91/// Later versions may add types, so handle a value you don't know (eg, with a `default` case).
92///
93/// @see Value::type()
94///
95enum class Type : unsigned {
96 Invalid = kULJSType_Invalid, ///< An empty Value, or one whose page is gone.
97 Undefined = kULJSType_Undefined,
98 Null = kULJSType_Null,
99 Boolean = kULJSType_Boolean,
100 Number = kULJSType_Number,
101 String = kULJSType_String,
102 Symbol = kULJSType_Symbol,
103 BigInt = kULJSType_BigInt,
104 Object = kULJSType_Object,
105};
106
107static_assert(kULJSType_Count == 9, "js::Type must list every ULJSType");
108
109///
110/// A handle to a live JavaScript value.
111///
112/// A Value represents a live JavaScript value on a page. You use it to hold page objects and
113/// functions across calls, interacting with them directly without converting them to C++ types.
114///
115/// This example interacts with an object on the page:
116///
117/// ```
118/// js::Value player = ctx["game"]["player"];
119///
120/// double health = player["health"].Or(0.0); // read (0.0 if anything fails)
121/// player["score"] = 1200; // write
122/// player["respawn"](10, 20); // call a method (`this` is player)
123/// ```
124///
125/// ## Handle States
126///
127/// A handle is always in one of these states:
128///
129/// | State | When | operator bool | Calls |
130/// |-------|--------------------------------------------------|---------------|---------------|
131/// | Valid | On a live page | `true` | Work normally |
132/// | Empty | Default-constructed, moved from, or failed read | `false` | Do nothing |
133/// | Gone | Page navigated, frame removed, or View destroyed | `false` | Do nothing |
134///
135/// An empty Value differs from JavaScript `null` and `undefined`. A missing property reads as a
136/// valid Value holding `undefined`, while an empty handle represents an uninitialized handle or a
137/// failed operation.
138///
139/// Every operation is safe in all three states, allowing property reads and method calls to chain
140/// without checking each step.
141///
142/// This call chains safely even if the HUD object doesn't exist:
143///
144/// ```
145/// ctx["hud"]["showToast"]("Saved"); // does nothing if there's no hud
146/// ```
147///
148/// When an operation returns a failed js::Result, inspect js::Error::is_empty() to check for an
149/// empty handle or js::Error::is_page_gone() to check whether the page navigated away.
150///
151/// A handle whose page is gone never recovers, even when that page is restored from the
152/// back-forward cache.
153///
154/// To interact with the new page, get a new Value from that page's js::Context. Every page-lifetime
155/// handle across the JavaScript API follows these rules.
156///
157/// ## Copies and Lifetimes
158///
159/// Copying a Value creates another reference to the same JavaScript value.
160///
161/// Holding a Value keeps the underlying JavaScript value from being garbage-collected, but it
162/// doesn't keep the page or its View alive.
163///
164/// For ownership cycles between bound instances and JavaScript objects, see js::ClassBuilder.
165///
166/// A `const Value` still lets you change the underlying JavaScript value, matching the behavior of
167/// a `const RefPtr`.
168///
169/// ## Type Conversions
170///
171/// The conversion methods To(), Maybe(), and Or() are strict about the JavaScript type (the string
172/// `"7"` doesn't convert to a number), differing only in how they report a failure.
173///
174/// To convert values using JavaScript's own coercion rules, call ToNumber(), ToString(),
175/// ToBoolean(), or ToJSON() directly on the Value. For the full list of supported types, see
176/// js::TypeTraits.
177///
178/// This example compares strict conversion against JavaScript coercion:
179///
180/// ```
181/// js::Value level = player["level"]; // the string "7"
182///
183/// double strict = level.Or(0.0); // 0.0 (not a number)
184/// double loose = level.ToNumber().value_or(0.0); // 7.0 (JavaScript's rules)
185/// ```
186///
187/// ## Values Across Pages
188///
189/// A Value belongs to the page that created it. You can pass a Value directly only between frames
190/// of the same top-level page that share an exact origin, never across separate View%s.
191///
192/// Using a Value on a page that can't take it fails with a TypeError whose error code is
193/// `ULJS_CROSS_CONTEXT`. To transfer data between separate pages or View%s, copy the data using
194/// JSON or native C++ types.
195///
196/// This example transfers player data to a page in another View using JSON:
197///
198/// ```
199/// js::Context bestiary(bestiary_view.get()); // a page in another View
200///
201/// std::string json = player.ToJSON().value_or("null");
202/// bestiary["player"] = js::OrEmpty(bestiary.MakeFromJSON(json));
203/// ```
204///
205/// @note js::Value and dom::data::Value are unrelated types (dom::data::Value is the value type of
206/// the data-binding API).
207///
208/// @see js::Context, js::Error, js::TypeTraits, js::WeakValue
209///
210class Value {
211 public:
212 ///
213 /// Create an empty Value.
214 ///
215 Value() : handle_(nullptr) {}
216
217 ///
218 /// Copy constructor (adds a reference to the same JavaScript value).
219 ///
220 Value(const Value& other)
221 : handle_(other.handle_ ? ulCreateJSValueRef(other.handle_) : nullptr) {}
222
223 ///
224 /// Move constructor (`other` becomes empty).
225 ///
226 Value(Value&& other) noexcept : handle_(other.handle_) { other.handle_ = nullptr; }
227
228 ///
229 /// Assignment (copies or moves `other` into this Value).
230 ///
231 Value& operator=(Value other) noexcept {
232 std::swap(handle_, other.handle_);
233 return *this;
234 }
235
236 ///
237 /// Destructor (releases this handle).
238 ///
239 ~Value() { ulDestroyJSValue(handle_); }
240
241 ///
242 /// Whether or not this Value is valid (it isn't empty and its page is still alive).
243 ///
244 explicit operator bool() const { return IsAlive(); }
245
246 ///
247 /// Whether or not this Value holds nothing (see "Handle States" above).
248 ///
249 bool IsEmpty() const { return handle_ == nullptr; }
250
251 ///
252 /// Whether or not this Value's page is still alive (the same test as `operator bool`).
253 ///
254 /// @note Safe to call from any thread, but off the Renderer's thread the result may already
255 /// be out of date when you act on it.
256 ///
257 bool IsAlive() const { return handle_ && ulJSValueIsAlive(handle_); }
258
259 ///
260 /// Get the type of the value.
261 ///
262 /// @return Returns the value's type (Type::Invalid for an empty Value or one whose page is
263 /// gone).
264 ///
265 Type type() const { return static_cast<Type>(ulJSValueGetType(handle_)); }
266
267 ///
268 /// Whether or not the value is `undefined`.
269 ///
270 bool IsUndefined() const { return type() == Type::Undefined; }
271
272 ///
273 /// Whether or not the value is `null`.
274 ///
275 bool IsNull() const { return type() == Type::Null; }
276
277 ///
278 /// Whether or not the value is `null` or `undefined`.
279 ///
280 bool IsNullish() const { return IsNull() || IsUndefined(); }
281
282 ///
283 /// Whether or not the value is a boolean.
284 ///
285 bool IsBoolean() const { return type() == Type::Boolean; }
286
287 ///
288 /// Whether or not the value is a number.
289 ///
290 bool IsNumber() const { return type() == Type::Number; }
291
292 ///
293 /// Whether or not the value is a BigInt (eg, `10n`).
294 ///
295 bool IsBigInt() const { return type() == Type::BigInt; }
296
297 ///
298 /// Whether or not the value is a string.
299 ///
300 bool IsString() const { return type() == Type::String; }
301
302 ///
303 /// Whether or not the value is an object.
304 ///
305 /// Arrays and functions are objects too (use IsArray() or IsFunction() to tell them apart).
306 ///
307 bool IsObject() const { return type() == Type::Object; }
308
309 ///
310 /// Whether or not the value is an Array.
311 ///
312 bool IsArray() const { return handle_ && ulJSValueIsArray(handle_); }
313
314 ///
315 /// Whether or not the value can be called (see Call() and Invoke()).
316 ///
317 bool IsCallable() const { return handle_ && ulJSValueIsCallable(handle_); }
318
319 ///
320 /// Whether or not the value is a function.
321 ///
322 /// @note A few callable objects aren't functions (eg, the constructor of a native class). Use
323 /// IsCallable() to check whether you can call a value.
324 ///
325 bool IsFunction() const { return handle_ && ulJSValueIsFunction(handle_); }
326
327 ///
328 /// Whether or not the value is a Promise.
329 ///
330 /// This has no side effects: the promise isn't marked as handled.
331 ///
332 bool IsPromise() const { return handle_ && ulJSValueIsPromise(handle_); }
333
334 ///
335 /// Convert to a boolean using JavaScript's rules (this never runs script).
336 ///
337 /// @return Returns the boolean.
338 ///
339 [[nodiscard]] Result<bool> ToBoolean() const {
340 if (!handle_)
341 return detail::EmptyHandleError();
342 bool result;
343 if (!ulJSValueToBoolean(handle_, &result))
344 return Unexpected<Error>(Error::PageGone());
345 return result;
346 }
347
348 ///
349 /// Convert to a number using JavaScript's rules.
350 ///
351 /// @return Returns the number. Fails with a JavaScript exception if the conversion throws
352 /// (eg, a valueOf() method throws or the value is a Symbol).
353 ///
354 [[nodiscard]] Result<double> ToNumber() const {
355 if (!handle_)
356 return detail::EmptyHandleError();
357 double result;
358 ULJSValue exception = nullptr;
359 if (!ulJSValueToNumber(handle_, &result, &exception)) {
360 if (exception)
361 return Unexpected<Error>(Error::AdoptException(exception));
362 return Unexpected<Error>(Error::PageGone());
363 }
364 return result;
365 }
366
367 ///
368 /// Convert to a UTF-8 string using JavaScript's rules.
369 ///
370 /// @return Returns the string. Fails with a JavaScript exception if the conversion throws
371 /// (eg, a toString() method throws or the value is a Symbol).
372 ///
373 [[nodiscard]] Result<std::string> ToString() const {
374 if (!handle_)
375 return detail::EmptyHandleError();
376 ULJSValue exception = nullptr;
377 if (ulJSValueGetType(handle_) == kULJSType_String) {
378 // Already a string: reading its bytes runs no script, so the capacity retry is safe.
379 char stack_buffer[256];
380 size_t length = 0;
381 if (ulJSValueGetUTF8(handle_, stack_buffer, sizeof(stack_buffer), &length, &exception))
382 return std::string(stack_buffer, length);
383 if (!exception && length > sizeof(stack_buffer)) {
384 std::string heap(length, '\0');
385 if (ulJSValueGetUTF8(handle_, heap.data(), heap.size(), &length, &exception))
386 return heap;
387 }
388 if (exception)
389 return Unexpected<Error>(Error::AdoptException(exception));
390 return Unexpected<Error>(Error::PageGone());
391 }
392 ULString s = ulJSValueToString(handle_, &exception);
393 if (!s) {
394 if (exception)
395 return Unexpected<Error>(Error::AdoptException(exception));
396 return Unexpected<Error>(Error::PageGone());
397 }
398 std::string result(ulStringGetData(s), ulStringGetLength(s));
399 ulDestroyString(s);
400 return result;
401 }
402
403 ///
404 /// Convert to JSON text like `JSON.stringify()` does.
405 ///
406 /// @param indent The number of spaces to indent each level by (0 for compact output, at
407 /// most 10).
408 ///
409 /// @return Returns the JSON text. Fails with a TypeError if the value has no JSON form
410 /// (`undefined`, a function, or a Symbol), or with a JavaScript exception if a
411 /// toJSON() method or a getter throws.
412 ///
413 [[nodiscard]] Result<std::string> ToJSON(unsigned indent = 0) const {
414 if (!handle_)
415 return detail::EmptyHandleError();
416 ULJSValue exception = nullptr;
417 ULString s = ulJSValueToJSON(handle_, indent, &exception);
418 if (!s) {
419 if (exception)
420 return Unexpected<Error>(Error::AdoptException(exception));
421 // JSON.stringify renders some live values as no JSON at all (undefined, functions,
422 // symbols); report that as its own failure, not as a page-gone error.
423 if (ulJSValueIsAlive(handle_))
424 return Unexpected<Error>(Error::TypeError("The value has no JSON representation."));
425 return Unexpected<Error>(Error::PageGone());
426 }
427 std::string result(ulStringGetData(s), ulStringGetLength(s));
428 ulDestroyString(s);
429 return result;
430 }
431
432 ///
433 /// Get a property of this object (own or inherited).
434 ///
435 /// @param name The property name (NULL is treated as "").
436 ///
437 /// @return Returns the property value (`undefined` if there's no such property). Fails with
438 /// a JavaScript exception if a getter throws.
439 ///
440 [[nodiscard]] Result<Value> GetProperty(const char* name) const {
441 if (!handle_)
442 return detail::EmptyHandleError();
443 ULJSValue exception = nullptr;
444 ULJSValue result = ulJSObjectGetProperty(handle_, name ? name : "", &exception);
445 if (!result) {
446 if (exception)
447 return Unexpected<Error>(Error::AdoptException(exception));
448 return Unexpected<Error>(Error::PageGone());
449 }
450 return Adopt(result);
451 }
452
453 ///
454 /// Whether or not this object has a property (own or inherited).
455 ///
456 /// Unlike reading the property, this returns true for a property set to `undefined`. It
457 /// doesn't run getters.
458 ///
459 /// @param name The property name (NULL is treated as "").
460 ///
461 /// @note Any failure returns false. Use HasProperty() if you need the reason.
462 ///
463 bool Has(const char* name) const {
464 return handle_ && ulJSObjectHasProperty(handle_, name ? name : "", nullptr);
465 }
466
467 ///
468 /// Whether or not this object has a property (like Has(), but reports failures).
469 ///
470 /// @param name The property name (NULL is treated as "").
471 ///
472 /// @return Returns whether the property exists. Fails with a JavaScript exception if the
473 /// check throws (eg, in a Proxy's `has` handler).
474 ///
475 [[nodiscard]] Result<bool> HasProperty(const char* name) const {
476 if (!handle_)
477 return detail::EmptyHandleError();
478 ULJSValue exception = nullptr;
479 bool present = ulJSObjectHasProperty(handle_, name ? name : "", &exception);
480 if (exception)
481 return Unexpected<Error>(Error::AdoptException(exception));
482 if (!IsAlive())
483 return Unexpected<Error>(Error::PageGone());
484 return present;
485 }
486
487 ///
488 /// Set a property of this object.
489 ///
490 /// ```
491 /// obj.SetProperty("id", ctx.Make(7.0),
492 /// js::PropertyAttributes::ReadOnly | js::PropertyAttributes::DontDelete);
493 /// ```
494 ///
495 /// @param name The property name (NULL is treated as "").
496 ///
497 /// @param value The value to assign (an empty Value assigns `undefined`).
498 ///
499 /// @param attributes The flags combined with `|`. They only apply if this creates the
500 /// property (see PropertyAttributes).
501 ///
502 /// @return Returns success. Fails with a JavaScript exception if a setter throws.
503 ///
504 /// @note Like an assignment in non-strict JavaScript, a write the object refuses (a frozen
505 /// object, or a read-only property) is silently ignored and still returns success.
506 ///
508 const char* name, const Value& value,
509 PropertyAttributes attributes = PropertyAttributes::None) const {
510 if (!handle_)
511 return detail::EmptyHandleError();
512 ULJSValue exception = nullptr;
513 if (ulJSObjectSetProperty(handle_, name ? name : "", value.handle_,
514 static_cast<unsigned>(attributes), &exception))
515 return {};
516 if (exception)
517 return Unexpected<Error>(Error::AdoptException(exception));
518 return Unexpected<Error>(Error::PageGone());
519 }
520
521 ///
522 /// Call this value as a function.
523 ///
524 /// @param this_value The `this` value for the call (an empty Value uses the global object).
525 ///
526 /// @param args The arguments (an empty Value passes `undefined`).
527 ///
528 /// @param argc The number of entries in `args`.
529 ///
530 /// @return Returns the function's return value. Fails with a JavaScript exception if the
531 /// call throws or this value can't be called.
532 ///
533 [[nodiscard]] Result<Value> Call(const Value& this_value, const Value* args,
534 size_t argc) const {
535 if (!handle_)
536 return detail::EmptyHandleError();
537 ULJSValue raw_args[8];
538 ULJSValue* arg_ptr = raw_args;
539 if (argc > 8) {
540 // Rare wide calls take one heap allocation; typical calls stay on the stack.
541 arg_ptr = new ULJSValue[argc];
542 }
543 for (size_t i = 0; i < argc; i++)
544 arg_ptr[i] = args[i].handle_;
545 ULJSValue exception = nullptr;
546 ULJSValue result = ulJSFunctionCall(handle_, this_value.handle_, arg_ptr, argc, &exception);
547 if (arg_ptr != raw_args)
548 delete[] arg_ptr;
549 if (!result) {
550 if (exception)
551 return Unexpected<Error>(Error::AdoptException(exception));
552 return Unexpected<Error>(Error::PageGone());
553 }
554 return Adopt(result);
555 }
556
557 ///
558 /// Call this value as a function with the arguments in a range (eg, a
559 /// `std::vector<js::Value>`).
560 ///
561 /// @param this_value The `this` value for the call (an empty Value uses the global object).
562 ///
563 /// @param args The arguments.
564 ///
565 /// @return Returns the function's return value and fails the same way as the overload
566 /// above.
567 ///
568 [[nodiscard]] Result<Value> Call(const Value& this_value,
569 std::span<const Value> args) const {
570 return Call(this_value, args.data(), args.size());
571 }
572
573 ///
574 /// Convert to a C++ type.
575 ///
576 /// This works for every type js::TypeTraits supports: numbers, strings, containers,
577 /// optionals, variants, reflected structs and enums, and your own specializations. The value
578 /// must already have the matching JavaScript type (see "Type Conversions" above).
579 ///
580 /// ```
581 /// js::Result<Settings> s = payload["settings"].To<Settings>();
582 ///
583 /// // To walk an array or an object's entries, convert to a container of js::Value:
584 /// auto items = arr.To<std::vector<js::Value>>().value_or({});
585 /// auto entries = obj.To<std::map<std::string, js::Value>>().value_or({});
586 /// ```
587 ///
588 /// An integer type needs a whole number in its range, so `To<int>()` on 3.5 fails instead of
589 /// truncating.
590 ///
591 /// @return Returns the converted value. Fails with a TypeError (code `ULJS_BAD_ARG`) if the
592 /// value doesn't have the requested type. Its message says where and why, like a
593 /// bound function's (eg, "volume: expected number, got 'loud'").
594 ///
595 /// @note Requires `<Ultralight/js/TypeTraits.h>` (or any header that includes it).
596 ///
597 template <typename T>
598 [[nodiscard]] Result<T> To() const;
599
600 ///
601 /// Convert to a C++ type like To() but without the failure reason.
602 ///
603 /// @return Returns the converted value or nullopt on any failure.
604 ///
605 template <typename T>
606 [[nodiscard]] std::optional<T> Maybe() const;
607
608 ///
609 /// Convert to a C++ type like To() but with a fallback.
610 ///
611 /// @param fallback The value to return if the conversion fails.
612 ///
613 /// @return Returns the converted value or `fallback` on any failure.
614 ///
615 /// @note The fallback's type is the type converted to, so pass `0.0` (not `0`) to read a
616 /// number that can have a fraction: `Or(0)` converts to `int` and fails on 3.5.
617 ///
618 template <typename T>
619 [[nodiscard]] T Or(T fallback) const;
620
621 ///
622 /// Convert to a std::string like To() but with a string-literal fallback.
623 ///
624 /// @param fallback The text to return if the conversion fails.
625 ///
626 /// @return Returns the converted string or `fallback` on any failure.
627 ///
628 [[nodiscard]] std::string Or(const char* fallback) const;
629
630 ///
631 /// Call this value as a function with typed arguments and result.
632 ///
633 /// The arguments and the result (when you give R) convert through js::TypeTraits:
634 ///
635 /// ```
636 /// js::Result<double> total = fn.Invoke<double>(3, "x");
637 /// fn.Invoke(path); // result left as a js::Value
638 /// ```
639 ///
640 /// @param args The arguments to pass to the function.
641 ///
642 /// @return Returns the call's result converted to R. Fails with a JavaScript exception if
643 /// the call throws, or with a TypeError if the result doesn't convert to R.
644 ///
645 /// @note The function's `this` is the global object. To call a method on an object, call
646 /// through operator[] (`obj["m"](x)`) or use InvokeOn().
647 ///
648 template <typename R = Value, typename... A>
649 Result<R> Invoke(A&&... args) const;
650
651 ///
652 /// Call this value as a function with an explicit `this` (otherwise the same as Invoke()).
653 ///
654 /// ```
655 /// js::Result<js::Value> row = fn.InvokeOn(receiver, 42);
656 /// ```
657 ///
658 /// @param this_value The `this` value for the call.
659 ///
660 /// @param args The arguments to pass to the function.
661 ///
662 /// @return Returns the call's result and fails the same way as Invoke().
663 ///
664 template <typename R = Value, typename... A>
665 Result<R> InvokeOn(const Value& this_value, A&&... args) const;
666
667 ///
668 /// Call this value as a function: `on_save(path)` is the same as `on_save.Invoke(path)`.
669 ///
670 /// @param args The arguments to pass to the function.
671 ///
672 /// @return Returns the call's result and fails the same way as Invoke().
673 ///
674 template <typename... A>
675 Result<Value> operator()(A&&... args) const {
676 return Invoke<Value>(std::forward<A>(args)...);
677 }
678
679 ///
680 /// Wait for this promise to settle without a coroutine.
681 ///
682 /// `on_settled` runs once on the Renderer's thread during a later Renderer::Update(). Its
683 /// Result holds the promise's value or an Error with the rejection reason. If the page goes
684 /// away first, it gets a page-gone Error instead, so it always runs.
685 ///
686 /// ```
687 /// // Page: myApp.nativeReady(fetch('/level.json').then(r => r.json()));
688 /// api["nativeReady"] = [](js::Value promise) {
689 /// bool waiting = promise.Then([](js::Result<js::Value> r) {
690 /// if (r)
691 /// LoadLevel(*r); // fulfilled: r holds the value
692 /// else if (!r.error().is_page_gone())
693 /// Log(r.error().message()); // rejected: r.error() holds the reason
694 /// });
695 /// if (!waiting)
696 /// Log("nativeReady expects a Promise");
697 /// };
698 /// ```
699 ///
700 /// @param on_settled A callable that takes a js::Result<js::Value>.
701 ///
702 /// @return Returns true if this value is a Promise and `on_settled` will run. Otherwise it
703 /// returns false and destroys `on_settled` without running it.
704 ///
705 /// \parblock
706 /// @note A rejection you wait for this way counts as handled, so the page doesn't report it
707 /// as unhandled.
708 /// \endparblock
709 ///
710 /// \parblock
711 /// @note In a coroutine, use `co_await js::Await<T>(promise)` instead (see
712 /// `<Ultralight/js/Task.h>`).
713 /// \endparblock
714 ///
715 template <typename F>
716 [[nodiscard]] bool Then(F&& on_settled) const {
717 using Fn = std::decay_t<F>;
718 static_assert(std::is_invocable_v<Fn&, Result<Value>>,
719 "js::Value::Then takes a callable invocable with js::Result<js::Value>");
720 auto* fn = new Fn(std::forward<F>(on_settled));
721 // On registration failure the library destroys the observer inline, freeing `fn`.
722 return ulJSPromiseThen(handle_, &ThenThunk<Fn>, fn, &DeleteThenCallable<Fn>);
723 }
724
725 ///
726 /// Wait for this promise to settle and convert its value to a C++ type.
727 ///
728 /// ```
729 /// bool waiting = promise.Then<std::string>([](js::Result<std::string> r) {
730 /// if (r)
731 /// ApplyName(*r);
732 /// });
733 /// ```
734 ///
735 /// @param on_settled A callable that takes a js::Result<T>.
736 ///
737 /// @return Returns true if this value is a Promise and `on_settled` will run (see Then()).
738 ///
739 /// @note If the value doesn't convert to T (see To()), `on_settled` gets the TypeError.
740 ///
741 template <typename T, typename F>
742 requires(!std::is_same_v<T, Value>)
743 [[nodiscard]] bool Then(F&& on_settled) const {
744 using Fn = std::decay_t<F>;
745 static_assert(std::is_invocable_v<Fn&, Result<T>>,
746 "js::Value::Then<T> takes a callable invocable with js::Result<T>");
747 return Then(TypedSettleAdapter<T, Fn> { std::forward<F>(on_settled) });
748 }
749
750 class Ref;
751
752 ///
753 /// Access a property to read, assign, or call it.
754 ///
755 /// ```
756 /// double volume = settings["volume"].Or(0.0);
757 /// settings["volume"] = 5.0;
758 /// settings["nested"]["deep"] = js::null;
759 /// settings["save"]("slot1"); // method call: `this` = settings
760 /// ```
761 ///
762 /// A read that fails (the Value is empty or its page is gone, it holds `null` or `undefined`,
763 /// or a getter throws) gives you an empty Value instead of an error, like JavaScript's
764 /// optional chaining. Use GetProperty() or SetProperty() when you need the reason.
765 ///
766 /// @param name The property name.
767 ///
768 /// @return Returns a Value::Ref for the property.
769 ///
770 /// \parblock
771 /// @note A missing property reads as a valid `undefined` Value (`operator bool` returns
772 /// true). Use Has() to check whether a property exists.
773 /// \endparblock
774 ///
775 /// \parblock
776 /// @note You can also index with any integer type (eg, `arr[2]`). A negative index reads the
777 /// property with that name as JavaScript does (`arr[-1]` reads "-1").
778 /// \endparblock
779 ///
780 Ref operator[](const char* name) const;
781 template <typename I>
782 requires(std::is_integral_v<I> && !std::is_same_v<I, bool>)
783 Ref operator[](I index) const;
784
785 // --- Interop with the C API (most embedders never touch raw handles) -------------------
786
787 ///
788 /// Take ownership of a handle from the C API without adding a reference.
789 ///
790 /// Use this for a handle you'd otherwise destroy with ulDestroyJSValue() (eg, the return value
791 /// of a C function).
792 ///
793 /// @param handle The handle to take ownership of.
794 ///
795 /// @return Returns a Value that owns `handle`.
796 ///
797 /// @note For a handle the library owns (eg, a callback argument), use FromBorrowed().
798 ///
799 static Value Adopt(ULJSValue handle) { return Value(handle); }
800
801 ///
802 /// Add a reference to a handle the library owns (eg, a callback argument) so it can outlive
803 /// the callback.
804 ///
805 /// @param handle The borrowed handle.
806 ///
807 /// @return Returns a Value with its own reference to `handle`.
808 ///
809 /// @note For a handle you own, use Adopt().
810 ///
811 static Value FromBorrowed(ULJSValue handle) {
812 return Value(handle ? ulCreateJSValueRef(handle) : nullptr);
813 }
814
815 ///
816 /// Get the C API handle without transferring ownership.
817 ///
818 /// @return Returns the handle for use with the `<Ultralight/CAPI/CAPI_JSValue.h>` functions.
819 ///
820 ULJSValue raw() const { return handle_; }
821
822 ///
823 /// Give up ownership of the C API handle and return it (like RefPtr::LeakRef()).
824 ///
825 /// @return Returns the handle. You must destroy it with ulDestroyJSValue() when finished.
826 ///
827 ULJSValue LeakRef() {
828 ULJSValue handle = handle_;
829 handle_ = nullptr;
830 return handle;
831 }
832
833 protected:
834 explicit Value(ULJSValue handle) : handle_(handle) {}
835
836 private:
837 // Decimal property name for integer indexes JavaScript treats as string keys.
838 struct IndexKey {
839 char text[24];
840 };
841
842 static IndexKey MakeIndexKey(long long value) {
843 IndexKey key;
844 snprintf(key.text, sizeof(key.text), "%lld", value);
845 return key;
846 }
847
848 static IndexKey MakeIndexKey(unsigned long long value) {
849 IndexKey key;
850 snprintf(key.text, sizeof(key.text), "%llu", value);
851 return key;
852 }
853
854 template <typename I>
855 Ref MakeIndexRef(I index) const;
856
857 // Named functor (never a lambda: the MSVC alias-in-lambda discipline) bridging the
858 // untyped settlement to Then<T>'s typed callback.
859 template <typename T, typename Fn>
860 struct TypedSettleAdapter {
861 Fn fn;
862 void operator()(Result<Value> settled) {
863 if (!settled)
864 fn(Result<T>(Unexpected<Error>(std::move(settled).error())));
865 else
866 fn(settled.value().To<T>());
867 }
868 };
869
870 template <typename Fn>
871 static void ThenThunk(void* user_data, ULJSContext, ULJSValue result, bool rejected) {
872 Fn& fn = *static_cast<Fn*>(user_data);
873 // The callback runs inside the library's update: an exception must not unwind into it.
874 ultralight::detail::CallCatchingExceptions(
875 [&] {
876 if (!result)
877 fn(Result<Value>(Unexpected<Error>(Error::PageGone())));
878 else if (rejected)
879 fn(Result<Value>(
880 Unexpected<Error>(Error::AdoptException(ulCreateJSValueRef(result)))));
881 else
883 },
884 [](const char* what) { detail::LogNativeException("js::Value::Then", what); });
885 }
886
887 template <typename Fn>
888 static void DeleteThenCallable(void* user_data) {
889 delete static_cast<Fn*>(user_data);
890 }
891
892 ULJSValue handle_;
893};
894
895///
896/// A reference to a property of a Value (returned by Value::operator[]).
897///
898/// Reading a Ref gives you the property's value, assigning to it sets the property, and calling
899/// it calls the property as a method of the object. Refs chain (`obj["a"]["b"] = 1`).
900///
901/// Use a Ref within one expression. To keep a property's value, read it into a Value.
902///
903/// @note A Ref has To(), Maybe(), and Or(). For the other conversions, read the property first
904/// (eg, `obj["volume"].Get().ToNumber()`).
905///
907 public:
908 ///
909 /// Read the property.
910 ///
911 /// @return Returns the property value (an empty Value if the read fails, see
912 /// Value::operator[]()).
913 ///
914 [[nodiscard]] Value Get() const {
915 detail::SwallowedException swallow;
916 if (by_index_)
917 return Value::Adopt(
918 object_.raw() ? ulJSObjectGetPropertyAtIndex(object_.raw(), index_, swallow.out())
919 : nullptr);
920 return Value::Adopt(
921 object_.raw() ? ulJSObjectGetProperty(object_.raw(), name_.c_str(), swallow.out())
922 : nullptr);
923 }
924
925 ///
926 /// Read the property (the same as Get()).
927 ///
928 operator Value() const { return Get(); }
929
930 ///
931 /// Set the property to `value` (converted through js::TypeTraits).
932 ///
933 /// @param value The value to assign.
934 ///
935 /// @return Returns this Ref.
936 ///
937 /// @note Failures are ignored (eg, the page is gone or a setter throws). Use
938 /// Value::SetProperty() if you need to check.
939 ///
940 template <typename T>
941 Ref& operator=(T&& value);
942
943 ///
944 /// Read this property and access a property of its value.
945 ///
946 /// @param name The property name.
947 ///
948 /// @return Returns a Ref for the nested property.
949 ///
950 Ref operator[](const char* name) const {
951 Value value = Get();
952 return Chained(value, value[name]);
953 }
954 template <typename I>
955 requires(std::is_integral_v<I> && !std::is_same_v<I, bool>)
956 Ref operator[](I index) const {
957 Value value = Get();
958 return Chained(value, value[index]);
959 }
960
961 ///
962 /// Call the property as a method of its object (otherwise the same as Value::Invoke()).
963 ///
964 /// ```
965 /// global["JSON"]["stringify"](payload);
966 /// js::Result<double> total = cart["total"].Invoke<double>();
967 /// ```
968 ///
969 /// @param args The arguments to pass to the method.
970 ///
971 /// @return Returns the call's result converted to R and fails the same way as
972 /// Value::Invoke().
973 ///
974 template <typename R = Value, typename... A>
975 Result<R> Invoke(A&&... args) const;
976
977 ///
978 /// Call the property as a method: `obj["save"](path)` is the same as `obj["save"].Invoke(path)`.
979 ///
980 /// @param args The arguments to pass to the method.
981 ///
982 /// @return Returns the call's result and fails the same way as Invoke().
983 ///
984 template <typename... A>
985 Result<Value> operator()(A&&... args) const {
986 return Invoke<Value>(std::forward<A>(args)...);
987 }
988
989 ///
990 /// Read the property and convert it to a C++ type (see Value::To()).
991 ///
992 /// @return Returns the converted value and fails the same way as Value::To().
993 ///
994 template <typename T>
995 [[nodiscard]] Result<T> To() const;
996
997 ///
998 /// Read the property and convert it to a C++ type like To() but without the failure reason.
999 ///
1000 /// @return Returns the converted value or nullopt on any failure.
1001 ///
1002 template <typename T>
1003 [[nodiscard]] std::optional<T> Maybe() const;
1004
1005 ///
1006 /// Read the property and convert it to a C++ type with a fallback.
1007 ///
1008 /// @param fallback The value to return if the read or the conversion fails.
1009 ///
1010 /// @return Returns the converted value or `fallback` on any failure.
1011 ///
1012 template <typename T>
1013 [[nodiscard]] T Or(T fallback) const;
1014
1015 ///
1016 /// Read the property and convert it to a std::string with a string-literal fallback.
1017 ///
1018 /// @param fallback The text to return if the read or the conversion fails.
1019 ///
1020 /// @return Returns the converted string or `fallback` on any failure.
1021 ///
1022 [[nodiscard]] std::string Or(const char* fallback) const { return Get().Or(fallback); }
1023
1024 ///
1025 /// Copy constructor (refers to the same property).
1026 ///
1027 Ref(const Ref&) = default;
1028
1029 ///
1030 /// Set this property to the value of another Ref's property.
1031 ///
1032 /// @param other The Ref to read the value from.
1033 ///
1034 /// @return Returns this Ref.
1035 ///
1036 /// @note A Ref always refers to the same property: assigning to it sets the property's value
1037 /// and never changes which property it refers to (like std::map's operator[]).
1038 ///
1039 Ref& operator=(const Ref& other) { return (*this) = other.Get(); }
1040
1041 private:
1042 friend class Value;
1043 friend class Context;
1044 Ref(Value object, const char* name)
1045 : object_(std::move(object)), name_(name ? name : ""), by_index_(false), index_(0) {}
1046 Ref(Value object, size_t index)
1047 : object_(std::move(object)), by_index_(true), index_(index) {}
1048
1049 // Whether a failed read here is because the page is gone: this Ref's object belongs to a page
1050 // that's gone, or the chain it came from started at one.
1051 bool SourceGone() const { return source_gone_ || (object_.raw() && !object_.IsAlive()); }
1052
1053 // A chain continued through a failed read keeps reporting a gone page as page-gone, not as an
1054 // empty handle.
1055 Ref Chained(const Value& value, Ref next) const {
1056 next.source_gone_ = value.IsEmpty() && SourceGone();
1057 return next;
1058 }
1059
1060 Value object_;
1061 std::string name_;
1062 bool by_index_;
1063 size_t index_;
1064 bool source_gone_ = false;
1065};
1066
1067inline Value::Ref Value::operator[](const char* name) const {
1068 return Ref(*this, name);
1069}
1070
1071template <typename I>
1072Value::Ref Value::MakeIndexRef(I index) const {
1073 if constexpr (std::is_signed_v<I>) {
1074 if (index < 0)
1075 return Ref(*this, MakeIndexKey(static_cast<long long>(index)).text);
1076 }
1077 unsigned long long wide = static_cast<unsigned long long>(index);
1078 if (wide > SIZE_MAX)
1079 return Ref(*this, MakeIndexKey(wide).text);
1080 return Ref(*this, static_cast<size_t>(wide));
1081}
1082
1083template <typename I>
1084 requires(std::is_integral_v<I> && !std::is_same_v<I, bool>)
1085Value::Ref Value::operator[](I index) const {
1086 return MakeIndexRef(index);
1087}
1088
1089///
1090/// Compare against the JavaScript `null` literal: `if (v == js::null)`.
1091///
1092/// @return Returns whether the value is JavaScript `null`.
1093///
1094inline bool operator==(const Value& value, NullType) {
1095 return value.IsNull();
1096}
1097
1098///
1099/// Compare against the JavaScript `undefined` literal: `if (v == js::undefined)`.
1100///
1101/// @return Returns whether the value is JavaScript `undefined`.
1102///
1103inline bool operator==(const Value& value, UndefinedType) {
1104 return value.IsUndefined();
1105}
1106
1107///
1108/// An argument passed to a bound function.
1109///
1110/// An Arg is only valid during the callback that received it (it adds no reference, so reading
1111/// it is cheap). Call ToValue() to keep the argument after the callback returns.
1112///
1113/// An Arg has To(), Maybe(), and Or(). Other conversions need a Value first (eg,
1114/// `arg.ToValue().ToNumber()`).
1115///
1116/// @see CallInfo::arg()
1117///
1118class Arg {
1119 public:
1120 ///
1121 /// Wrap a borrowed handle.
1122 ///
1123 /// @param borrowed The handle (valid only during the callback).
1124 ///
1125 /// @note You normally get an Arg from CallInfo::arg() instead of creating one.
1126 ///
1127 explicit Arg(ULJSValue borrowed) : borrowed_(borrowed) {}
1128
1129 ///
1130 /// Whether or not the argument is absent (the call passed fewer arguments than the index
1131 /// given to CallInfo::arg()).
1132 ///
1133 /// @note An absent argument converts only to an optional type (as an empty optional). Any
1134 /// other To() fails with a TypeError, and ToValue() returns an empty Value.
1135 ///
1136 bool IsEmpty() const { return borrowed_ == nullptr; }
1137
1138 ///
1139 /// Get the argument's type (Type::Invalid if the argument is absent).
1140 ///
1141 Type type() const { return static_cast<Type>(ulJSValueGetType(borrowed_)); }
1142
1143 ///
1144 /// Convert to a C++ type (see Value::To()).
1145 ///
1146 /// @return Returns the converted value and fails the same way as Value::To().
1147 ///
1148 /// @note Requires `<Ultralight/js/TypeTraits.h>` (or any header that includes it).
1149 ///
1150 template <typename T>
1151 [[nodiscard]] Result<T> To() const;
1152
1153 ///
1154 /// Convert to a C++ type like To() but without the failure reason.
1155 ///
1156 /// @return Returns the converted value or nullopt on any failure.
1157 ///
1158 template <typename T>
1159 [[nodiscard]] std::optional<T> Maybe() const;
1160
1161 ///
1162 /// Convert to a C++ type like To() but with a fallback.
1163 ///
1164 /// @param fallback The value to return if the conversion fails.
1165 ///
1166 /// @return Returns the converted value or `fallback` on any failure.
1167 ///
1168 template <typename T>
1169 [[nodiscard]] T Or(T fallback) const;
1170
1171 ///
1172 /// Convert to a std::string like To() but with a string-literal fallback.
1173 ///
1174 /// @param fallback The text to return if the conversion fails.
1175 ///
1176 /// @return Returns the converted string or `fallback` on any failure.
1177 ///
1178 [[nodiscard]] std::string Or(const char* fallback) const;
1179
1180 ///
1181 /// Get a Value that keeps the argument after the callback returns.
1182 ///
1183 /// @return Returns the new Value.
1184 ///
1185 Value ToValue() const { return Value::FromBorrowed(borrowed_); }
1186
1187 ///
1188 /// Get the C API handle.
1189 ///
1190 /// @return Returns the handle (valid only during the callback).
1191 ///
1192 ULJSValue raw() const { return borrowed_; }
1193
1194 private:
1195 ULJSValue borrowed_;
1196};
1197
1198///
1199/// Convert a value to a C++ type with a fallback (the free-function form of Value::Or()).
1200///
1201/// This takes a Value, a Value::Ref, a js::Arg, or the Result of a call. The value must already
1202/// have the requested JavaScript type (see Value::To()). Any failure returns `fallback` (eg, a
1203/// missing property or a failed call):
1204///
1205/// ```
1206/// double score = js::Or(ctx["score"], 0.0);
1207/// double total = js::Or(ctx["computeTotal"](3, 4), 0.0);
1208/// std::string name = js::Or(ctx["playerName"](), "guest");
1209/// ```
1210///
1211/// @param value The value to convert.
1212///
1213/// @param fallback The value to return if the conversion fails.
1214///
1215/// @return Returns the converted value or `fallback` on any failure.
1216///
1217/// @note Requires `<Ultralight/js/TypeTraits.h>` (or any header that includes it).
1218///
1219template <typename T>
1220[[nodiscard]] T Or(const Value& value, T fallback) {
1221 return value.Or(std::move(fallback));
1222}
1223
1224///
1225/// Convert a value to a std::string with a string-literal fallback.
1226///
1227/// @param value The value to convert.
1228///
1229/// @param fallback The text to return if the conversion fails.
1230///
1231/// @return Returns the converted string or `fallback` on any failure.
1232///
1233[[nodiscard]] inline std::string Or(const Value& value, const char* fallback) {
1234 return value.Or(fallback);
1235}
1236
1237///
1238/// Convert a callback argument to a C++ type with a fallback.
1239///
1240/// @param arg The argument to convert.
1241///
1242/// @param fallback The value to return if the conversion fails.
1243///
1244/// @return Returns the converted value or `fallback` on any failure.
1245///
1246template <typename T>
1247[[nodiscard]] T Or(const Arg& arg, T fallback) {
1248 return arg.Or(std::move(fallback));
1249}
1250
1251///
1252/// Convert a callback argument to a std::string with a string-literal fallback.
1253///
1254/// @param arg The argument to convert.
1255///
1256/// @param fallback The text to return if the conversion fails.
1257///
1258/// @return Returns the converted string or `fallback` on any failure.
1259///
1260[[nodiscard]] inline std::string Or(const Arg& arg, const char* fallback) {
1261 return arg.Or(fallback);
1262}
1263
1264///
1265/// Convert the value a call returned to a C++ type with a fallback.
1266///
1267/// @param result The call's result.
1268///
1269/// @param fallback The value to return if the call or the conversion failed.
1270///
1271/// @return Returns the converted value or `fallback` on any failure.
1272///
1273template <typename R, typename T>
1274 requires std::is_same_v<std::remove_cvref_t<R>, Result<Value>>
1275[[nodiscard]] T Or(const R& result, T fallback) {
1276 return result ? result.value().Or(std::move(fallback)) : fallback;
1277}
1278
1279///
1280/// Convert the value a call returned to a std::string with a string-literal fallback.
1281///
1282/// @param result The call's result.
1283///
1284/// @param fallback The text to return if the call or the conversion failed.
1285///
1286/// @return Returns the converted string or `fallback` on any failure.
1287///
1288template <typename R>
1289 requires std::is_same_v<std::remove_cvref_t<R>, Result<Value>>
1290[[nodiscard]] std::string Or(const R& result, const char* fallback) {
1291 return result ? result.value().Or(fallback) : std::string(fallback ? fallback : "");
1292}
1293
1294///
1295/// Get a typed Result's value or `fallback` if it holds an error.
1296///
1297/// This is the same as `result.value_or(fallback)` and lets a typed call read like an untyped
1298/// one: `js::Or(fn.Invoke<double>(3, 4), 0.0)`.
1299///
1300/// @param result The typed result.
1301///
1302/// @param fallback The value to return if the result holds an error.
1303///
1304/// @return Returns the result's value or `fallback`.
1305///
1306template <typename T, typename U>
1307 requires(!std::is_same_v<T, Value>)
1308[[nodiscard]] T Or(const Result<T>& result, U&& fallback) {
1309 return result.value_or(std::forward<U>(fallback));
1310}
1311
1312///
1313/// Get the value a JavaScript exception threw, to keep it or pass it back to the page.
1314///
1315/// ```
1316/// js::Result<js::Value> r = ctx.Evaluate("JSON.parse('{')");
1317/// if (!r && r.error().is_exception())
1318/// ctx["lastError"] = js::ExceptionValue(r.error());
1319/// ```
1320///
1321/// @param error The error.
1322///
1323/// @return Returns the thrown value (an empty Value if `error` doesn't hold a JavaScript
1324/// exception).
1325///
1326inline Value ExceptionValue(const Error& error) {
1327 return Value::FromBorrowed(error.exception());
1328}
1329
1330} // namespace js
1331} // namespace ultralight
1332
1333#pragma pop_macro("None")
struct C_JSContext * ULJSContext
Opaque handle to a JavaScript execution context (one page's script world in one View).
Definition View.h:25
Unicode string container with conversions for UTF-8, UTF-16, and UTF-32.
Definition String.h:31
An argument passed to a bound function.
Definition Value.h:1118
std::string Or(const char *fallback) const
Convert to a std::string like To() but with a string-literal fallback.
T Or(T fallback) const
Convert to a C++ type like To() but with a fallback.
bool IsEmpty() const
Whether or not the argument is absent (the call passed fewer arguments than the index given to CallIn...
Definition Value.h:1136
Result< T > To() const
Convert to a C++ type (see Value::To()).
std::optional< T > Maybe() const
Convert to a C++ type like To() but without the failure reason.
Type type() const
Get the argument's type (Type::Invalid if the argument is absent).
Definition Value.h:1141
Arg(ULJSValue borrowed)
Wrap a borrowed handle.
Definition Value.h:1127
Value ToValue() const
Get a Value that keeps the argument after the callback returns.
Definition Value.h:1185
ULJSValue raw() const
Get the C API handle.
Definition Value.h:1192
An error from a JavaScript operation.
Definition Error.h:115
static Error AdoptException(ULJSValue exception)
Create an error from a JavaScript exception, taking ownership of the handle.
Definition Error.h:131
static Error TypeError(std::string message)
Create a native TypeError.
Definition Error.h:144
static Error PageGone()
Create a page-gone error (is_page_gone() returns true).
Definition Error.h:122
ULJSValue exception() const
Get the JavaScript exception (NULL if is_exception() is false).
Definition Error.h:268
A reference to a property of a Value (returned by Value::operator[]).
Definition Value.h:906
Ref operator[](const char *name) const
Read this property and access a property of its value.
Definition Value.h:950
Ref & operator=(const Ref &other)
Set this property to the value of another Ref's property.
Definition Value.h:1039
Ref(const Ref &)=default
Copy constructor (refers to the same property).
std::string Or(const char *fallback) const
Read the property and convert it to a std::string with a string-literal fallback.
Definition Value.h:1022
T Or(T fallback) const
Read the property and convert it to a C++ type with a fallback.
Ref & operator=(T &&value)
Set the property to value (converted through js::TypeTraits).
Value Get() const
Read the property.
Definition Value.h:914
Result< R > Invoke(A &&... args) const
Call the property as a method of its object (otherwise the same as Value::Invoke()).
Result< Value > operator()(A &&... args) const
Call the property as a method: obj["save"](path) is the same as obj["save"].Invoke(path).
Definition Value.h:985
Result< T > To() const
Read the property and convert it to a C++ type (see Value::To()).
friend class Context
Definition Value.h:1043
std::optional< T > Maybe() const
Read the property and convert it to a C++ type like To() but without the failure reason.
friend class Value
Definition Value.h:1042
A handle to a live JavaScript value.
Definition Value.h:210
bool Has(const char *name) const
Whether or not this object has a property (own or inherited).
Definition Value.h:463
Ref operator[](const char *name) const
Access a property to read, assign, or call it.
Definition Value.h:1067
Value(Value &&other) noexcept
Move constructor (other becomes empty).
Definition Value.h:226
Value & operator=(Value other) noexcept
Assignment (copies or moves other into this Value).
Definition Value.h:231
std::string Or(const char *fallback) const
Convert to a std::string like To() but with a string-literal fallback.
static Value Adopt(ULJSValue handle)
Take ownership of a handle from the C API without adding a reference.
Definition Value.h:799
bool IsCallable() const
Whether or not the value can be called (see Call() and Invoke()).
Definition Value.h:317
static Value FromBorrowed(ULJSValue handle)
Add a reference to a handle the library owns (eg, a callback argument) so it can outlive the callback...
Definition Value.h:811
Result< double > ToNumber() const
Convert to a number using JavaScript's rules.
Definition Value.h:354
Result< std::string > ToString() const
Convert to a UTF-8 string using JavaScript's rules.
Definition Value.h:373
ULJSValue LeakRef()
Give up ownership of the C API handle and return it (like RefPtr::LeakRef()).
Definition Value.h:827
T Or(T fallback) const
Convert to a C++ type like To() but with a fallback.
Value(const Value &other)
Copy constructor (adds a reference to the same JavaScript value).
Definition Value.h:220
Result< R > InvokeOn(const Value &this_value, A &&... args) const
Call this value as a function with an explicit this (otherwise the same as Invoke()).
Result< bool > HasProperty(const char *name) const
Whether or not this object has a property (like Has(), but reports failures).
Definition Value.h:475
bool IsNumber() const
Whether or not the value is a number.
Definition Value.h:290
Result< bool > ToBoolean() const
Convert to a boolean using JavaScript's rules (this never runs script).
Definition Value.h:339
bool IsObject() const
Whether or not the value is an object.
Definition Value.h:307
bool IsPromise() const
Whether or not the value is a Promise.
Definition Value.h:332
bool IsString() const
Whether or not the value is a string.
Definition Value.h:300
bool Then(F &&on_settled) const
Wait for this promise to settle and convert its value to a C++ type.
Definition Value.h:743
bool IsEmpty() const
Whether or not this Value holds nothing (see "Handle States" above).
Definition Value.h:249
bool IsAlive() const
Whether or not this Value's page is still alive (the same test as operator bool).
Definition Value.h:257
~Value()
Destructor (releases this handle).
Definition Value.h:239
Result< std::string > ToJSON(unsigned indent=0) const
Convert to JSON text like JSON.stringify() does.
Definition Value.h:413
Result< R > Invoke(A &&... args) const
Call this value as a function with typed arguments and result.
Result< Value > operator()(A &&... args) const
Call this value as a function: on_save(path) is the same as on_save.Invoke(path).
Definition Value.h:675
Result< Value > GetProperty(const char *name) const
Get a property of this object (own or inherited).
Definition Value.h:440
Result< T > To() const
Convert to a C++ type.
Value(ULJSValue handle)
Definition Value.h:834
bool IsNullish() const
Whether or not the value is null or undefined.
Definition Value.h:280
Value()
Create an empty Value.
Definition Value.h:215
Result< void > SetProperty(const char *name, const Value &value, PropertyAttributes attributes=PropertyAttributes::None) const
Set a property of this object.
Definition Value.h:507
bool IsBigInt() const
Whether or not the value is a BigInt (eg, 10n).
Definition Value.h:295
bool IsNull() const
Whether or not the value is null.
Definition Value.h:275
bool IsBoolean() const
Whether or not the value is a boolean.
Definition Value.h:285
Result< Value > Call(const Value &this_value, std::span< const Value > args) const
Call this value as a function with the arguments in a range (eg, a std::vector<js::Value>).
Definition Value.h:568
std::optional< T > Maybe() const
Convert to a C++ type like To() but without the failure reason.
Type type() const
Get the type of the value.
Definition Value.h:265
bool IsUndefined() const
Whether or not the value is undefined.
Definition Value.h:270
bool Then(F &&on_settled) const
Wait for this promise to settle without a coroutine.
Definition Value.h:716
Result< Value > Call(const Value &this_value, const Value *args, size_t argc) const
Call this value as a function.
Definition Value.h:533
bool IsFunction() const
Whether or not the value is a function.
Definition Value.h:325
bool IsArray() const
Whether or not the value is an Array.
Definition Value.h:312
ULJSValue raw() const
Get the C API handle without transferring ownership.
Definition Value.h:820
Definition StringSTL.h:166
Type-checked JavaScript bridge between C++ and web pages.
Definition JSInterop.h:141
constexpr UndefinedType undefined
The JavaScript undefined value.
Definition Value.h:58
T Or(const Value &value, T fallback)
Convert a value to a C++ type with a fallback (the free-function form of Value::Or()).
Definition Value.h:1220
Type
The type of a JavaScript value.
Definition Value.h:95
@ Symbol
Definition Value.h:102
@ String
Definition Value.h:101
@ Boolean
Definition Value.h:99
@ Object
Definition Value.h:104
@ Invalid
An empty Value, or one whose page is gone.
Definition Value.h:96
@ BigInt
Definition Value.h:103
@ Number
Definition Value.h:100
@ Null
Definition Value.h:98
@ Undefined
Definition Value.h:97
constexpr NullType null
The JavaScript null value.
Definition Value.h:48
Value ExceptionValue(const Error &error)
Get the value a JavaScript exception threw, to keep it or pass it back to the page.
Definition Value.h:1326
constexpr AttachFlags operator|(AttachFlags a, AttachFlags b)
Combine attach flags.
Definition API.h:56
PropertyAttributes
Attribute flags for a property created by Value::SetProperty().
Definition Value.h:72
@ DontDelete
Script can't delete it.
Definition Value.h:76
@ ReadOnly
Script can't change its value.
Definition Value.h:74
@ DontEnum
Hidden from for...in and Object.keys.
Definition Value.h:75
@ None
No flags (a normal property).
Definition Value.h:73
bool operator==(const Value &value, NullType)
Compare against the JavaScript null literal: if (v == js::null).
Definition Value.h:1094
Expected< T, Error > Result
The result of a JavaScript operation that can fail: a T or a js::Error.
Definition Error.h:520
Root namespace for every public Ultralight type, function, and enumeration.
@ None
Definition Anchor.h:36
Tag type for the JavaScript null value (see js::null).
Definition Value.h:40
Type conversions between C++ and JavaScript across the bridge.
Definition TypeTraits.h:240
Tag type for the JavaScript undefined value (see js::undefined).
Definition Value.h:53