Skip to content

Commit 9783e3d

Browse files
committed
feat: javascript helpers extension
fix #881
1 parent 64ebd9b commit 9783e3d

File tree

3 files changed

+240
-32
lines changed

3 files changed

+240
-32
lines changed

include/mrdocs/Support/JavaScript.hpp

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,6 @@ class Handlebars;
5757
*/
5858
namespace js {
5959

60-
/** Grants friend-level access to internal JS bridge scopes.
61-
*/
62-
struct Access;
6360
class Context;
6461
class Scope;
6562

@@ -135,7 +132,6 @@ class MRDOCS_DECL
135132
private:
136133
std::shared_ptr<Impl> impl_;
137134

138-
friend struct Access;
139135
friend class Value;
140136
friend class Scope;
141137

@@ -198,8 +194,6 @@ class Scope
198194
{
199195
std::shared_ptr<Context::Impl> impl_;
200196

201-
friend struct Access;
202-
203197
public:
204198
/** Constructor.
205199
@@ -428,7 +422,6 @@ class MRDOCS_DECL Value
428422
/// Opaque engine value handle stored as an integer (engine-specific inside the implementation).
429423
std::uint32_t val_;
430424

431-
friend struct Access;
432425
friend class Scope;
433426

434427
/** Wrap an existing engine value without transferring ownership.

src/lib/Support/JavaScript.cpp

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ invokeHelper(Value const& fn, dom::Array const& args)
7777
// of symbol contexts which contain circular references.
7878
std::vector<dom::Value> callArgs;
7979
callArgs.reserve(args.size() - 1);
80-
for (auto it = args.begin(); it != std::prev(args.end()); ++it)
80+
for (std::size_t i = 0; i < args.size() - 1; ++i)
8181
{
82-
callArgs.push_back(*it);
82+
callArgs.push_back(args.get(i));
8383
}
8484

8585
auto ret = fn.apply(callArgs);
@@ -128,6 +128,12 @@ toString(jerry_value_t v)
128128
// - Runtime errors (thrown exceptions) are prefixed with "Unexpected: " if they
129129
// don't already contain that marker, helping distinguish them from parse errors
130130
// - This prefix is intentionally consistent to aid debugging and testing
131+
//
132+
// LIMITATION: The "Unexpected" heuristic isn't perfect - some runtime errors
133+
// may contain "Unexpected" in their message and won't get the prefix, while
134+
// some custom syntax-like errors might get prefixed incorrectly. This is
135+
// acceptable because the prefix is for debugging convenience, not semantic
136+
// correctness.
131137
static Error
132138
makeError(jerry_value_t exc)
133139
{
@@ -138,8 +144,12 @@ makeError(jerry_value_t exc)
138144
std::string msg;
139145
if (jerry_value_is_object(obj))
140146
{
141-
jerry_value_t msg_prop
142-
= jerry_object_get(obj, jerry_string_sz("message"));
147+
// Note: jerry_string_sz is used here instead of makeString because
148+
// makeString is defined later in this file and we need to extract
149+
// error messages early in the error handling path.
150+
jerry_value_t msg_key = jerry_string_sz("message");
151+
jerry_value_t msg_prop = jerry_object_get(obj, msg_key);
152+
jerry_value_free(msg_key);
143153
if (!jerry_value_is_exception(msg_prop))
144154
{
145155
msg = toString(msg_prop);
@@ -245,12 +255,23 @@ struct Context::Impl {
245255
std::mutex Context::Impl::init_mtx;
246256
unsigned Context::Impl::jerry_refcount = 0;
247257

258+
// Acquire the context lock for thread-safe JerryScript operations.
259+
//
260+
// Thread Safety Model:
261+
// - JerryScript is single-threaded; all engine calls must be serialized.
262+
// - Each Context::Impl has a recursive_mutex protecting its JerryScript state.
263+
// - Values hold shared_ptr<Impl> to keep the context alive and use this
264+
// function before any jerry_* calls.
265+
// - The recursive_mutex allows nested locking (e.g., a trap callback that
266+
// needs to call toJsValue which also locks).
267+
//
268+
// @param impl Shared context state, or nullptr for moved-from Values.
269+
// @return RAII lock guard; empty if impl is null.
248270
static std::unique_lock<std::recursive_mutex>
249271
lockContext(std::shared_ptr<Context::Impl> const& impl)
250272
{
251-
// Prevent concurrent use of a JerryScript context without requiring callers
252-
// to remember which locks to take. Accepts null shared_ptr so callers can
253-
// use it uniformly in move/copy paths.
273+
// Accepts null shared_ptr so callers can use it uniformly in move/copy
274+
// paths where the source Value may have been moved-from (val_ == 0).
254275
if (impl)
255276
{
256277
return std::unique_lock<std::recursive_mutex>(impl->mtx);
@@ -985,8 +1006,9 @@ Value::size() const
9851006
if (isArray())
9861007
{
9871008
auto lock = lockContext(impl_);
988-
jerry_value_t lenVal
989-
= jerry_object_get(to_js(val_), jerry_string_sz("length"));
1009+
jerry_value_t lenKey = makeString("length");
1010+
jerry_value_t lenVal = jerry_object_get(to_js(val_), lenKey);
1011+
jerry_value_free(lenKey);
9901012
if (jerry_value_is_exception(lenVal))
9911013
{
9921014
jerry_value_free(lenVal);
@@ -1172,7 +1194,7 @@ makeObjectProxy(dom::Object obj, std::shared_ptr<Context::Impl> impl)
11721194
return toJsValue(val, h->impl);
11731195
});
11741196

1175-
jerry_value_t get_key = jerry_string_sz("get");
1197+
jerry_value_t get_key = makeString("get");
11761198
jerry_value_t sr = jerry_object_set(handler, get_key, get_fn);
11771199
jerry_value_free(sr);
11781200
jerry_value_free(get_key);
@@ -1195,7 +1217,7 @@ makeObjectProxy(dom::Object obj, std::shared_ptr<Context::Impl> impl)
11951217
return jerry_boolean(h->value.getObject().exists(propName));
11961218
});
11971219

1198-
jerry_value_t has_key = jerry_string_sz("has");
1220+
jerry_value_t has_key = makeString("has");
11991221
sr = jerry_object_set(handler, has_key, has_fn);
12001222
jerry_value_free(sr);
12011223
jerry_value_free(has_key);
@@ -1221,15 +1243,15 @@ makeObjectProxy(dom::Object obj, std::shared_ptr<Context::Impl> impl)
12211243
jerry_value_t arr = jerry_array(keys.size());
12221244
for (uint32_t i = 0; i < keys.size(); ++i)
12231245
{
1224-
jerry_value_t keyVal = jerry_string_sz(keys[i].c_str());
1246+
jerry_value_t keyVal = makeString(keys[i]);
12251247
jerry_value_t setRes = jerry_object_set_index(arr, i, keyVal);
12261248
jerry_value_free(setRes);
12271249
jerry_value_free(keyVal);
12281250
}
12291251
return arr;
12301252
});
12311253

1232-
jerry_value_t ownKeys_key = jerry_string_sz("ownKeys");
1254+
jerry_value_t ownKeys_key = makeString("ownKeys");
12331255
sr = jerry_object_set(handler, ownKeys_key, ownKeys_fn);
12341256
jerry_value_free(sr);
12351257
jerry_value_free(ownKeys_key);
@@ -1257,53 +1279,58 @@ makeObjectProxy(dom::Object obj, std::shared_ptr<Context::Impl> impl)
12571279
jerry_value_t val = toJsValue(h->value.getObject().get(propName), h->impl);
12581280
jerry_value_t setRes;
12591281

1260-
jerry_value_t valueKey = jerry_string_sz("value");
1282+
jerry_value_t valueKey = makeString("value");
12611283
setRes = jerry_object_set(desc, valueKey, val);
12621284
jerry_value_free(setRes);
12631285
jerry_value_free(valueKey);
12641286
jerry_value_free(val);
12651287

1266-
jerry_value_t writableKey = jerry_string_sz("writable");
1288+
jerry_value_t writableKey = makeString("writable");
12671289
setRes = jerry_object_set(desc, writableKey, jerry_boolean(true));
12681290
jerry_value_free(setRes);
12691291
jerry_value_free(writableKey);
12701292

1271-
jerry_value_t enumKey = jerry_string_sz("enumerable");
1293+
jerry_value_t enumKey = makeString("enumerable");
12721294
setRes = jerry_object_set(desc, enumKey, jerry_boolean(true));
12731295
jerry_value_free(setRes);
12741296
jerry_value_free(enumKey);
12751297

1276-
jerry_value_t configKey = jerry_string_sz("configurable");
1298+
jerry_value_t configKey = makeString("configurable");
12771299
setRes = jerry_object_set(desc, configKey, jerry_boolean(true));
12781300
jerry_value_free(setRes);
12791301
jerry_value_free(configKey);
12801302

12811303
return desc;
12821304
});
12831305

1284-
jerry_value_t getOwnPropDesc_key = jerry_string_sz("getOwnPropertyDescriptor");
1306+
jerry_value_t getOwnPropDesc_key = makeString("getOwnPropertyDescriptor");
12851307
sr = jerry_object_set(handler, getOwnPropDesc_key, getOwnPropDesc_fn);
12861308
jerry_value_free(sr);
12871309
jerry_value_free(getOwnPropDesc_key);
12881310
jerry_value_free(getOwnPropDesc_fn);
12891311

1290-
// Store a reference to the holder on the handler so traps can access it
1312+
// Store a reference to the holder on the handler so traps can access it.
1313+
//
1314+
// Ownership chain: proxy → handler → holderRef → native_ptr(holder)
1315+
//
1316+
// When the proxy is garbage collected, handler is released, which releases
1317+
// holderRef, which triggers DomValueHolder::free_cb to delete the holder.
1318+
// This happens synchronously when refcounts reach zero (not deferred to GC).
12911319
jerry_value_t holderRef = jerry_object();
12921320
jerry_object_set_native_ptr(holderRef, &kDomProxyInfo, holder);
1293-
jerry_value_t holderKey = jerry_string_sz("__holder__");
1321+
jerry_value_t holderKey = makeString("__holder__");
12941322
sr = jerry_object_set(handler, holderKey, holderRef);
12951323
jerry_value_free(sr);
12961324
jerry_value_free(holderKey);
1297-
jerry_value_free(holderRef);
1325+
jerry_value_free(holderRef); // handler now owns the reference
12981326

12991327
// Create the proxy
13001328
jerry_value_t proxy = jerry_proxy(target, handler);
13011329
jerry_value_free(target);
1302-
jerry_value_free(handler);
1330+
jerry_value_free(handler); // proxy now owns handler (and transitively holder)
13031331

1304-
// If proxy creation fails, return an empty object rather than an exception.
1305-
// The holder is cleaned up via the free_cb when holderRef (attached to
1306-
// handler) is garbage collected.
1332+
// If proxy creation fails, handler was still freed above, which releases
1333+
// holderRef, which deletes holder via free_cb. Return empty object.
13071334
if (jerry_value_is_exception(proxy))
13081335
{
13091336
jerry_value_free(proxy);

0 commit comments

Comments
 (0)