docs
Loading...
Searching...
No Matches
WeakValue.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_JSRuntime.h>
7#include <Ultralight/detail/Exceptions.h>
10
11#include <type_traits>
12#include <utility>
13
14namespace ultralight {
15namespace js {
16
17///
18/// A weak reference to a JavaScript object.
19///
20/// Unlike a js::Value, a WeakValue doesn't keep its object alive, so the garbage collector can
21/// reclaim the object. Lock() gives you a Value for the object, or an empty Value once the
22/// object is collected or its page is gone:
23///
24/// ```
25/// js::WeakValue cached(heavy_object);
26/// // later:
27/// if (js::Value strong = cached.Lock())
28/// UseCached(strong);
29/// else
30/// RebuildCache();
31/// ```
32///
33/// Use a WeakValue for objects the page keeps alive by itself (eg, a cached page object or a
34/// bound instance's own JavaScript object).
35///
36/// @note WeakValue is move-only. For the thread rule, see the ultralight::js namespace
37/// overview (`<Ultralight/JS.h>`).
38///
39/// @warning Don't use a WeakValue as the only reference to a callback. If nothing in the page
40/// holds the callback, it's collected and disappears. See "Ownership Cycles" in
41/// `<Ultralight/js/Class.h>` for how to break a reference cycle instead.
42///
43class WeakValue {
44 public:
45 ///
46 /// Create an empty WeakValue (Lock() returns an empty Value).
47 ///
48 WeakValue() = default;
49
50 ///
51 /// Create a weak reference to `target`.
52 ///
53 /// @param target The object to observe. Anything else (a primitive, an empty Value, or a
54 /// Value whose page is gone) gives an empty WeakValue.
55 ///
56 explicit WeakValue(const Value& target) : weak_(ulCreateJSWeakRef(target.raw())) {}
57
58 ///
59 /// Destructor (releases the weak reference).
60 ///
61 ~WeakValue() { ulDestroyJSWeakRef(weak_); }
62
63 ///
64 /// Move constructor (`other` becomes empty).
65 ///
66 WeakValue(WeakValue&& other) noexcept : weak_(other.weak_) { other.weak_ = nullptr; }
67
68 ///
69 /// Move assignment (releases the current weak reference first).
70 ///
71 WeakValue& operator=(WeakValue&& other) noexcept {
72 if (this != &other) {
73 ulDestroyJSWeakRef(weak_);
74 weak_ = other.weak_;
75 other.weak_ = nullptr;
76 }
77 return *this;
78 }
79 WeakValue(const WeakValue&) = delete;
80 WeakValue& operator=(const WeakValue&) = delete;
81
82 ///
83 /// Get a Value for the object.
84 ///
85 /// @return Returns a valid Value for the object (an empty Value if the object was
86 /// collected, its page is gone, or this WeakValue is empty).
87 ///
88 [[nodiscard]] Value Lock() const { return Value::Adopt(ulJSWeakRefLock(weak_)); }
89
90 ///
91 /// Set a callback to run after the object is garbage collected.
92 ///
93 /// The callback runs once on the Renderer's thread during a later Renderer::Update(). Setting a
94 /// new callback replaces the previous one. You can destroy this WeakValue from inside the
95 /// callback.
96 ///
97 /// @param on_collected A callable that takes a js::Context& or nothing.
98 ///
99 /// @note The callback never runs if the object's page goes away first or this WeakValue is
100 /// destroyed first. On an empty WeakValue this does nothing.
101 ///
102 template <typename F>
103 void OnCollected(F&& on_collected) {
104 using Fn = std::decay_t<F>;
105 static_assert(std::is_invocable_v<Fn&> || std::is_invocable_v<Fn&, Context&>,
106 "js::WeakValue::OnCollected takes a callable invocable with nothing or "
107 "with js::Context&");
108 if (!weak_)
109 return;
110 ulJSWeakRefSetCollectedCallback(weak_, &CollectedThunk<Fn>,
111 new Fn(std::forward<F>(on_collected)),
112 &DeleteCollected<Fn>);
113 }
114
115 ///
116 /// Whether or not this WeakValue holds nothing (it was default-constructed, moved from,
117 /// released with LeakRef(), or created from something that isn't an object in a living page).
118 ///
119 /// This doesn't tell you whether the object still exists. Use Lock() for that.
120 ///
121 bool IsEmpty() const { return weak_ == nullptr; }
122
123 // --- Interop with the C API (most embedders never touch raw handles) -------------------
124
125 ///
126 /// Take ownership of a handle from the C API.
127 ///
128 /// @param handle The handle to take ownership of.
129 ///
130 /// @return Returns a WeakValue that owns `handle`.
131 ///
132 static WeakValue Adopt(ULJSWeakRef handle) {
133 WeakValue weak;
134 weak.weak_ = handle;
135 return weak;
136 }
137
138 ///
139 /// Get the C API handle without transferring ownership.
140 ///
141 /// @return Returns the handle for use with the `<Ultralight/CAPI/CAPI_JSRuntime.h>` functions.
142 ///
143 ULJSWeakRef raw() const { return weak_; }
144
145 ///
146 /// Give up ownership of the C API handle and return it.
147 ///
148 /// @return Returns the handle. You must destroy it with ulDestroyJSWeakRef() when finished.
149 ///
150 ULJSWeakRef LeakRef() {
151 ULJSWeakRef weak = weak_;
152 weak_ = nullptr;
153 return weak;
154 }
155
156 private:
157 template <typename Fn>
158 static void CollectedThunk(void* user_data, ULJSContext ctx) {
159 Fn& fn = *static_cast<Fn*>(user_data);
160 // The callback runs inside the library's update: an exception must not unwind into it.
161 ultralight::detail::CallCatchingExceptions(
162 [&] {
163 if constexpr (std::is_invocable_v<Fn&, Context&>) {
164 Context borrowed = Context::FromBorrowed(ctx);
165 fn(borrowed);
166 } else {
167 fn();
168 }
169 },
170 [](const char* what) { detail::LogNativeException("js::WeakValue::OnCollected", what); });
171 }
172
173 template <typename Fn>
174 static void DeleteCollected(void* user_data) {
175 delete static_cast<Fn*>(user_data);
176 }
177
178 ULJSWeakRef weak_ = nullptr;
179};
180
181} // namespace js
182} // namespace ultralight
struct C_JSContext * ULJSContext
Opaque handle to a JavaScript execution context (one page's script world in one View).
Definition View.h:25
JavaScript execution environment for a page.
Definition Context.h:151
static Context FromBorrowed(ULJSContext handle)
Add a reference to a context handle the library owns (eg, the context a C API callback receives) so y...
Definition Context.h:200
A handle to a live JavaScript value.
Definition Value.h:210
static Value Adopt(ULJSValue handle)
Take ownership of a handle from the C API without adding a reference.
Definition Value.h:799
WeakValue(const WeakValue &)=delete
static WeakValue Adopt(ULJSWeakRef handle)
Take ownership of a handle from the C API.
Definition WeakValue.h:132
~WeakValue()
Destructor (releases the weak reference).
Definition WeakValue.h:61
WeakValue & operator=(const WeakValue &)=delete
WeakValue(const Value &target)
Create a weak reference to target.
Definition WeakValue.h:56
void OnCollected(F &&on_collected)
Set a callback to run after the object is garbage collected.
Definition WeakValue.h:103
WeakValue(WeakValue &&other) noexcept
Move constructor (other becomes empty).
Definition WeakValue.h:66
bool IsEmpty() const
Whether or not this WeakValue holds nothing (it was default-constructed, moved from,...
Definition WeakValue.h:121
ULJSWeakRef raw() const
Get the C API handle without transferring ownership.
Definition WeakValue.h:143
ULJSWeakRef LeakRef()
Give up ownership of the C API handle and return it.
Definition WeakValue.h:150
WeakValue()=default
Create an empty WeakValue (Lock() returns an empty Value).
Value Lock() const
Get a Value for the object.
Definition WeakValue.h:88
WeakValue & operator=(WeakValue &&other) noexcept
Move assignment (releases the current weak reference first).
Definition WeakValue.h:71
Type-checked JavaScript bridge between C++ and web pages.
Definition JSInterop.h:141
Root namespace for every public Ultralight type, function, and enumeration.