1 | // Copyright (C) 2020 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include <QtQml/qjsmanagedvalue.h> |
5 | #include <QtQml/qjsengine.h> |
6 | #include <QtQml/private/qv4persistent_p.h> |
7 | #include <QtQml/private/qv4engine_p.h> |
8 | #include <QtQml/private/qv4mm_p.h> |
9 | #include <QtQml/private/qjsvalue_p.h> |
10 | #include <QtQml/private/qv4runtime_p.h> |
11 | #include <QtQml/private/qv4functionobject_p.h> |
12 | #include <QtQml/private/qv4jscall_p.h> |
13 | #include <QtQml/private/qv4urlobject_p.h> |
14 | #include <QtQml/private/qv4variantobject_p.h> |
15 | #include <QtQml/private/qv4qobjectwrapper_p.h> |
16 | #include <QtQml/private/qv4regexpobject_p.h> |
17 | #include <QtQml/private/qv4dateobject_p.h> |
18 | #include <QtQml/private/qv4errorobject_p.h> |
19 | #include <QtQml/private/qv4identifiertable_p.h> |
20 | |
21 | #include <QtCore/qregularexpression.h> |
22 | #include <QtCore/qurl.h> |
23 | #include <QtCore/qdatetime.h> |
24 | |
25 | QT_BEGIN_NAMESPACE |
26 | |
27 | /*! |
28 | * \class QJSManagedValue |
29 | * \inmodule QtQml |
30 | * \since 6.1 |
31 | * |
32 | * \inmodule QtQml |
33 | * |
34 | * \brief QJSManagedValue represents a value on the JavaScript heap belonging to a QJSEngine. |
35 | * |
36 | * The QJSManagedValue class allows interaction with JavaScript values in most |
37 | * ways you can interact with them from JavaScript itself. You can get and set |
38 | * properties and prototypes, and you can access arrays. Additionally, you can |
39 | * transform the value into the Qt counterparts of JavaScript objects. For |
40 | * example, a Url object may be transformed into a QUrl. |
41 | * |
42 | * A QJSManagedValue is always bound to a particular QJSEngine. You cannot use |
43 | * it independently. This means that you cannot have a QJSManagedValue from one |
44 | * engine be a property or a proptotype of a QJSManagedValue from a different |
45 | * engine. |
46 | * |
47 | * In contrast to QJSValue, almost all values held by QJSManagedValue live on |
48 | * the JavaScript heap. There is no inline or unmanaged storage. Therefore, you |
49 | * can get the prototype of a primitive value, and you can get the \c length |
50 | * property of a string. |
51 | * |
52 | * Only default-constructed or moved-from QJSManagedValues do not hold a value |
53 | * on the JavaScript heap. They represent \c undefined, which doesn't have any |
54 | * properties or prototypes. |
55 | * |
56 | * Also in contrast to QJSValue, QJSManagedValue does not catch any JavaScript |
57 | * exceptions. If an operation on a QJSManagedValue causes an error, it will |
58 | * generally return an \c undefined value and QJSEngine::hasError() will return |
59 | * \c true afterwards. You can then catch the exception using |
60 | * QJSEngine::catchError(), or pass it up the stack, at your own discretion. |
61 | * |
62 | * \note As the reference to the value on the JavaScript heap has to be freed |
63 | * on destruction, you cannot move a QJSManagedValue to a different thread. |
64 | * The destruction would take place in the new thread, which would create a race |
65 | * condition with the garbage collector on the original thread. This also means |
66 | * that you cannot hold a QJSManagedValue beyond the lifespan of its engine. |
67 | * |
68 | * The recommended way of working with a QJSManagedValue is creating it |
69 | * on the stack, possibly by moving a QJSValue and adding an engine, then |
70 | * performing the necessary operations on it, and finally moving it back into a |
71 | * QJSValue for storage. Moving between QJSManagedValue and QJSValue is fast. |
72 | */ |
73 | |
74 | /*! |
75 | * \enum QJSManagedValue::Type |
76 | * |
77 | * This enum represents the JavaScript native types, as specified by |
78 | * \l{ECMA-262}. |
79 | * |
80 | * \value Undefined The \c undefined type |
81 | * \value Boolean The \c boolean type |
82 | * \value Number The \c number type |
83 | * \value String The \c string type |
84 | * \value Object The \c object type |
85 | * \value Symbol The \c symbol type |
86 | * \value Function The \c function type |
87 | * |
88 | * Note that the \c null value is not a type of itself but rather a special kind |
89 | * of object. You can query a QJSManagedValue for this condition using the |
90 | * isNull() method. Furthermore, JavaScript has no integer type, but it knows a |
91 | * special treatment of numbers in preparation for integer only operations. You |
92 | * can query a QJSManagedValue to find out whether it holds the result of such a |
93 | * treatment by using the isInteger() method. |
94 | */ |
95 | |
96 | /*! |
97 | * \fn QJSManagedValue::QJSManagedValue() |
98 | * |
99 | * Creates a QJSManagedValue that represents the JavaScript \c undefined value. |
100 | * This is the only value not stored on the JavaScript heap. Calling engine() |
101 | * on a default-constructed QJSManagedValue will return nullptr. |
102 | */ |
103 | |
104 | static QV4::ExecutionEngine *v4Engine(QV4::Value *d) |
105 | { |
106 | if (!d) |
107 | return nullptr; |
108 | |
109 | QV4::ExecutionEngine *v4 = QV4::PersistentValueStorage::getEngine(v: d); |
110 | Q_ASSERT(v4); |
111 | return v4; |
112 | } |
113 | |
114 | /*! |
115 | * Creates a QJSManagedValue from \a value, using the heap of \a engine. If |
116 | * \a value is itself managed and the engine it belongs to is not \a engine, |
117 | * the result is an \c undefined value, and a warning is generated. |
118 | */ |
119 | QJSManagedValue::QJSManagedValue(QJSValue value, QJSEngine *engine) |
120 | { |
121 | QV4::ExecutionEngine *v4 = engine->handle(); |
122 | |
123 | if (QV4::Value *m = QJSValuePrivate::takeManagedValue(jsval: &value)) { |
124 | if (Q_UNLIKELY(v4Engine(m) != v4)) { |
125 | qWarning(msg: "QJSManagedValue(QJSValue, QJSEngine *) failed: " |
126 | "Value was created in different engine." ); |
127 | QV4::PersistentValueStorage::free(v: m); |
128 | return; |
129 | } |
130 | |
131 | d = m; |
132 | return; |
133 | } |
134 | |
135 | d = v4->memoryManager->m_persistentValues->allocate(); |
136 | |
137 | if (const QString *string = QJSValuePrivate::asQString(jsval: &value)) |
138 | *d = v4->newString(s: *string); |
139 | else |
140 | *d = QJSValuePrivate::asReturnedValue(jsval: &value); |
141 | } |
142 | |
143 | /*! |
144 | * Creates a QJSManagedValue from \a value using the heap of \a engine. |
145 | */ |
146 | QJSManagedValue::QJSManagedValue(const QJSPrimitiveValue &value, QJSEngine *engine) : |
147 | QJSManagedValue(engine->handle()) |
148 | { |
149 | switch (value.type()) { |
150 | case QJSPrimitiveValue::Undefined: |
151 | *d = QV4::Encode::undefined(); |
152 | return; |
153 | case QJSPrimitiveValue::Null: |
154 | *d = QV4::Encode::null(); |
155 | return; |
156 | case QJSPrimitiveValue::Boolean: |
157 | *d = QV4::Encode(value.asBoolean()); |
158 | return; |
159 | case QJSPrimitiveValue::Integer: |
160 | *d = QV4::Encode(value.asInteger()); |
161 | return; |
162 | case QJSPrimitiveValue::Double: |
163 | *d = QV4::Encode(value.asDouble()); |
164 | return; |
165 | case QJSPrimitiveValue::String: |
166 | *d = engine->handle()->newString(s: value.asString()); |
167 | return; |
168 | } |
169 | |
170 | Q_UNREACHABLE(); |
171 | } |
172 | |
173 | /*! |
174 | * Creates a QJSManagedValue from \a variant using the heap of \a engine. |
175 | */ |
176 | QJSManagedValue::QJSManagedValue(const QVariant &variant, QJSEngine *engine) : |
177 | QJSManagedValue(engine->handle()) |
178 | { |
179 | *d = engine->handle()->fromVariant(variant); |
180 | } |
181 | |
182 | /*! |
183 | * Creates a QJSManagedValue from \a string using the heap of \a engine. |
184 | */ |
185 | QJSManagedValue::QJSManagedValue(const QString &string, QJSEngine *engine) : |
186 | QJSManagedValue(engine->handle()) |
187 | { |
188 | *d = engine->handle()->newString(s: string); |
189 | } |
190 | |
191 | /*! |
192 | * Destroys the QJSManagedValue. |
193 | * |
194 | * \note This frees the memory slot it holds on the JavaScript heap. You must |
195 | * not destroy a QJSManagedValue from a different thread than the one |
196 | * where the QJSEngine it belongs to lives. |
197 | */ |
198 | QJSManagedValue::~QJSManagedValue() |
199 | { |
200 | QV4::PersistentValueStorage::free(v: d); |
201 | } |
202 | |
203 | /*! |
204 | * Move-constructs a QJSManagedValue from \a other. This leaves \a other in |
205 | * the default-constructed state where it represents undefined and does not |
206 | * belong to any engine. |
207 | */ |
208 | QJSManagedValue::QJSManagedValue(QJSManagedValue &&other) |
209 | { |
210 | qSwap(value1&: d, value2&: other.d); |
211 | } |
212 | |
213 | /*! |
214 | * Move-assigns a QJSManagedValue from \a other. This leaves \a other in |
215 | * the default-constructed state where it represents undefined and does not |
216 | * belong to any engine. |
217 | * |
218 | * \note This frees the memory slot this QJSManagedValue holds on the |
219 | * JavaScript heap. You must not move-assign a QJSManagedValue on a |
220 | * different thread than the one where the QJSEngine it belongs to lives. |
221 | */ |
222 | QJSManagedValue &QJSManagedValue::operator=(QJSManagedValue &&other) |
223 | { |
224 | if (this != &other) { |
225 | QV4::PersistentValueStorage::free(v: d); |
226 | d = nullptr; |
227 | qSwap(value1&: d, value2&: other.d); |
228 | } |
229 | return *this; |
230 | } |
231 | |
232 | /*! |
233 | * Invokes the JavaScript '==' operator on this QJSManagedValue and \a other, |
234 | * and returns the result. |
235 | * |
236 | * \sa strictlyEquals |
237 | */ |
238 | bool QJSManagedValue::equals(const QJSManagedValue &other) const |
239 | { |
240 | if (!d) |
241 | return !other.d || other.d->isNullOrUndefined(); |
242 | if (!other.d) |
243 | return d->isNullOrUndefined(); |
244 | |
245 | return QV4::Runtime::CompareEqual::call(*d, *other.d); |
246 | } |
247 | |
248 | /*! |
249 | * Invokes the JavaScript '===' operator on this QJSManagedValue and \a other, |
250 | * and returns the result. |
251 | * |
252 | * \sa equals |
253 | */ |
254 | bool QJSManagedValue::strictlyEquals(const QJSManagedValue &other) const |
255 | { |
256 | if (!d) |
257 | return !other.d || other.d->isUndefined(); |
258 | if (!other.d) |
259 | return d->isUndefined(); |
260 | |
261 | return QV4::RuntimeHelpers::strictEqual(x: *d, y: *other.d); |
262 | } |
263 | |
264 | /*! |
265 | * Returns the QJSEngine this QJSManagedValue belongs to. Mind that the engine |
266 | * is always valid, unless the QJSManagedValue is default-constructed or moved |
267 | * from. In the latter case a nullptr is returned. |
268 | */ |
269 | QJSEngine *QJSManagedValue::engine() const |
270 | { |
271 | if (!d) |
272 | return nullptr; |
273 | if (QV4::ExecutionEngine *v4 = QV4::PersistentValueStorage::getEngine(v: d)) |
274 | return v4->jsEngine(); |
275 | return nullptr; |
276 | } |
277 | |
278 | /*! |
279 | * Returns the prototype for this QJSManagedValue. This works on any value. You |
280 | * can, for example retrieve the JavaScript \c boolean prototype from a \c boolean |
281 | * value. |
282 | */ |
283 | QJSManagedValue QJSManagedValue::prototype() const |
284 | { |
285 | if (!d) |
286 | return QJSManagedValue(); |
287 | |
288 | QV4::ExecutionEngine *v4 = v4Engine(d); |
289 | QJSManagedValue result(v4); |
290 | |
291 | if (auto object = d->as<QV4::Object>()) |
292 | *result.d = object->getPrototypeOf(); |
293 | else if (auto managed = d->as<QV4::Managed>()) |
294 | *result.d = managed->internalClass()->prototype; |
295 | else if (d->isBoolean()) |
296 | *result.d = v4->booleanPrototype(); |
297 | else if (d->isNumber()) |
298 | *result.d = v4->numberPrototype(); |
299 | |
300 | // If the prototype appears to be undefined, then it's actually null in JS terms. |
301 | if (result.d->isUndefined()) |
302 | *result.d = QV4::Encode::null(); |
303 | |
304 | return result; |
305 | } |
306 | |
307 | /*! |
308 | * Sets the prototype of this QJSManagedValue to \a prototype. A precondition |
309 | * is that \a prototype belongs to the same QJSEngine as this QJSManagedValue |
310 | * and is an object (including null). Furthermore, this QJSManagedValue has to |
311 | * be an object (excluding null), too, and you cannot create prototype cycles. |
312 | */ |
313 | void QJSManagedValue::setPrototype(const QJSManagedValue &prototype) |
314 | { |
315 | auto object = d ? d->as<QV4::Object>() : nullptr; |
316 | if (!object) { |
317 | qWarning(msg: "QJSManagedValue::setPrototype() failed: " |
318 | "Can only set a prototype on an object (excluding null)." ); |
319 | return; |
320 | } |
321 | |
322 | // Object includes null ... |
323 | if (prototype.type() != QJSManagedValue::Object) { |
324 | qWarning(msg: "QJSManagedValue::setPrototype() failed: " |
325 | "Can only set objects (including null) as prototypes." ); |
326 | return; |
327 | } |
328 | |
329 | if (Q_UNLIKELY(object->engine() != v4Engine(prototype.d))) { |
330 | qWarning(msg: "QJSManagedValue::setPrototype() failed: " |
331 | "Prototype was created in differen engine." ); |
332 | return; |
333 | } |
334 | |
335 | // ... Null becomes nullptr here. That is why it appears as undefined later. |
336 | if (!object->setPrototypeOf(prototype.d->as<QV4::Object>())) { |
337 | qWarning(msg: "QJSManagedValue::setPrototype() failed: " |
338 | "Prototype cycle detected." ); |
339 | } |
340 | } |
341 | |
342 | /*! |
343 | * Returns the JavaScript type of this QJSManagedValue. |
344 | */ |
345 | QJSManagedValue::Type QJSManagedValue::type() const |
346 | { |
347 | if (!d || d->isUndefined()) |
348 | return Undefined; |
349 | if (d->isBoolean()) |
350 | return Boolean; |
351 | if (d->isNumber()) |
352 | return Number; |
353 | if (d->isString()) |
354 | return String; |
355 | if (d->isSymbol()) |
356 | return Symbol; |
357 | if (d->isFunctionObject()) |
358 | return Function; |
359 | return Object; |
360 | } |
361 | |
362 | /*! |
363 | * \fn QJSManagedValue::isUndefined() const |
364 | * |
365 | * Returns \c true if the type of this QJSManagedValue is \c undefined, |
366 | * or \c false otherwise. |
367 | */ |
368 | |
369 | /*! |
370 | * \fn QJSManagedValue::isBoolean() const |
371 | * |
372 | * Returns \c true if the type of this QJSManagedValue is \c boolean, |
373 | * or \c false otherwise. |
374 | */ |
375 | |
376 | /*! |
377 | * \fn QJSManagedValue::isNumber() const |
378 | * |
379 | * Returns \c true if the type of this QJSManagedValue is \c number, |
380 | * or \c false otherwise. |
381 | */ |
382 | |
383 | /*! |
384 | * \fn QJSManagedValue::isString() const |
385 | * |
386 | * Returns \c true if the type of this QJSManagedValue is \c string, |
387 | * or \c false otherwise. |
388 | */ |
389 | |
390 | /*! |
391 | * \fn QJSManagedValue::isSymbol() const |
392 | * |
393 | * Returns \c true if the type of this QJSManagedValue is \c symbol, |
394 | * or \c false otherwise. |
395 | */ |
396 | |
397 | /*! |
398 | * \fn QJSManagedValue::isObject() const |
399 | * |
400 | * Returns \c true if the type of this QJSManagedValue is \c object, |
401 | * or \c false otherwise. |
402 | */ |
403 | |
404 | /*! |
405 | * \fn QJSManagedValue::isFunction() const |
406 | * |
407 | * Returns \c true if the type of this QJSManagedValue is \c function, |
408 | * \c false otherwise. |
409 | */ |
410 | |
411 | /*! |
412 | * Returns \c true if this QJSManagedValue holds the JavaScript \c null value, |
413 | * or \c false otherwise. |
414 | */ |
415 | bool QJSManagedValue::isNull() const |
416 | { |
417 | return d && d->isNull(); |
418 | } |
419 | |
420 | /*! |
421 | * Returns \c true if this QJSManagedValue holds an integer value, or \c false |
422 | * otherwise. The storage format of a number does not affect the result of any |
423 | * operations performed on it, but if an integer is stored, many operations are |
424 | * faster. |
425 | */ |
426 | bool QJSManagedValue::isInteger() const |
427 | { |
428 | return d && d->isInteger(); |
429 | } |
430 | |
431 | /*! |
432 | * Returns \c true if this value represents a JavaScript regular expression |
433 | * object, or \c false otherwise. |
434 | */ |
435 | bool QJSManagedValue::isRegularExpression() const |
436 | { |
437 | return d && d->as<QV4::RegExpObject>(); |
438 | } |
439 | |
440 | /*! |
441 | * Returns \c true if this value represents a JavaScript Array |
442 | * object, or \c false otherwise. |
443 | */ |
444 | bool QJSManagedValue::isArray() const |
445 | { |
446 | return d && d->as<QV4::ArrayObject>(); |
447 | } |
448 | |
449 | /*! |
450 | * Returns \c true if this value represents a JavaScript Url |
451 | * object, or \c false otherwise. |
452 | */ |
453 | bool QJSManagedValue::isUrl() const |
454 | { |
455 | return d && d->as<QV4::UrlObject>(); |
456 | } |
457 | |
458 | /*! |
459 | * Returns \c true if this value represents a QVariant managed on the JavaScript |
460 | * heap, or \c false otherwise. |
461 | */ |
462 | bool QJSManagedValue::isVariant() const |
463 | { |
464 | return d && d->as<QV4::VariantObject>(); |
465 | } |
466 | |
467 | /*! |
468 | * Returns \c true if this value represents a QObject pointer managed on the |
469 | * JavaScript heap, or \c false otherwise. |
470 | */ |
471 | bool QJSManagedValue::isQObject() const |
472 | { |
473 | return d && d->as<QV4::QObjectWrapper>(); |
474 | } |
475 | |
476 | /*! |
477 | * Returns \c true if this value represents a QMetaObject pointer managed on the |
478 | * JavaScript heap, or \c false otherwise. |
479 | */ |
480 | bool QJSManagedValue::isQMetaObject() const |
481 | { |
482 | return d && d->as<QV4::QMetaObjectWrapper>(); |
483 | } |
484 | |
485 | /*! |
486 | * Returns \c true if this value represents a JavaScript Date object, or |
487 | * \c false otherwise. |
488 | */ |
489 | bool QJSManagedValue::isDate() const |
490 | { |
491 | return d && d->as<QV4::DateObject>(); |
492 | } |
493 | |
494 | /*! |
495 | * Returns \c true if this value represents a JavaScript Error object, or |
496 | * \c false otherwise. |
497 | */ |
498 | bool QJSManagedValue::isError() const |
499 | { |
500 | return d && d->as<QV4::ErrorObject>(); |
501 | } |
502 | |
503 | /*! |
504 | * \internal |
505 | * |
506 | * Returns \c true if this value represents a JavaScript meta type, or \c false |
507 | * otherwise. |
508 | */ |
509 | bool QJSManagedValue::isJsMetaType() const |
510 | { |
511 | return d && d->as<QV4::InternalClass>(); |
512 | } |
513 | |
514 | /*! |
515 | * Converts the manged value to a string. If the managed value holds a string, |
516 | * that one is returned. Otherwise a string coercion by JavaScript rules is |
517 | * performed. |
518 | * |
519 | * \note Conversion of a managed value to a string can throw an exception. In |
520 | * particular, symbols cannot be coerced into strings, or a custom |
521 | * toString() method may throw. In this case the result is an empty |
522 | * string and the engine carries an error after the conversion. |
523 | */ |
524 | QString QJSManagedValue::toString() const |
525 | { |
526 | return d ? d->toQString() : QStringLiteral("undefined" ); |
527 | } |
528 | |
529 | /*! |
530 | * Converts the manged value to a number. If the managed value holds a number, |
531 | * that one is returned. Otherwise a number coercion by JavaScript rules is |
532 | * performed. |
533 | * |
534 | * \note Conversion of a managed value to a number can throw an exception. In |
535 | * particular, symbols cannot be coerced into numbers, or a custom |
536 | * valueOf() method may throw. In this case the result is 0 and the |
537 | * engine carries an error after the conversion. |
538 | */ |
539 | double QJSManagedValue::toNumber() const |
540 | { |
541 | return d ? d->toNumber() : 0; |
542 | } |
543 | |
544 | /*! |
545 | * Converts the manged value to a boolean. If the managed value holds a boolean, |
546 | * that one is returned. Otherwise a boolean coercion by JavaScript rules is |
547 | * performed. |
548 | */ |
549 | bool QJSManagedValue::toBoolean() const |
550 | { |
551 | return d ? d->toBoolean() : false; |
552 | } |
553 | |
554 | /*! |
555 | * Converts the manged value to an integer. This first converts the value to a |
556 | * number by the rules of toNumber(), and then clamps it into the integer range |
557 | * by the rules given for coercing the arguments to JavaScript bit shift |
558 | * operators into 32bit integers. |
559 | * |
560 | * Internally, the value may already be stored as an integer, in which case a |
561 | * fast path is taken. |
562 | * |
563 | * \note Conversion of a managed value to a number can throw an exception. In |
564 | * particular, symbols cannot be coerced into numbers, or a custom |
565 | * valueOf() method may throw. In this case the result is 0 and the |
566 | * engine carries an error after the conversion. |
567 | * |
568 | * \note The JavaScript rules for coercing numbers into 32bit integers are |
569 | * unintuitive. |
570 | */ |
571 | int QJSManagedValue::toInteger() const |
572 | { |
573 | return d ? d->toInt32() : 0; |
574 | } |
575 | |
576 | /*! |
577 | * Converts the manged value to a QJSPrimitiveValue. If the managed value holds |
578 | * a type supported by QJSPrimitiveValue, the value is copied. Otherwise the |
579 | * value is converted to a string, and the string is stored in |
580 | * QJSPrimitiveValue. |
581 | * |
582 | * \note Conversion of a managed value to a string can throw an exception. In |
583 | * particular, symbols cannot be coerced into strings, or a custom |
584 | * toString() method may throw. In this case the result is the undefined |
585 | * value and the engine carries an error after the conversion. |
586 | */ |
587 | QJSPrimitiveValue QJSManagedValue::toPrimitive() const |
588 | { |
589 | if (!d || d->isUndefined()) |
590 | return QJSPrimitiveUndefined(); |
591 | if (d->isNull()) |
592 | return QJSPrimitiveNull(); |
593 | if (d->isBoolean()) |
594 | return d->booleanValue(); |
595 | if (d->isInteger()) |
596 | return d->integerValue(); |
597 | if (d->isNumber()) |
598 | return d->doubleValue(); |
599 | |
600 | bool ok; |
601 | const QString result = d->toQString(ok: &ok); |
602 | return ok ? QJSPrimitiveValue(result) : QJSPrimitiveValue(QJSPrimitiveUndefined()); |
603 | } |
604 | |
605 | /*! |
606 | * Copies this QJSManagedValue into a new QJSValue. This is less efficient than |
607 | * move-constructing a QJSValue from a QJSManagedValue, but retains the |
608 | * QJSManagedValue. |
609 | */ |
610 | QJSValue QJSManagedValue::toJSValue() const |
611 | { |
612 | return d ? QJSValuePrivate::fromReturnedValue(d: d->asReturnedValue()) : QJSValue(); |
613 | } |
614 | |
615 | /*! |
616 | * Copies this QJSManagedValue into a new QVariant. This also creates a useful |
617 | * QVariant if QJSManagedValue::isVariant() returns false. QVariant can hold all |
618 | * types supported by QJSManagedValue. |
619 | */ |
620 | QVariant QJSManagedValue::toVariant() const |
621 | { |
622 | if (!d || d->isUndefined()) |
623 | return QVariant(); |
624 | if (d->isNull()) |
625 | return QVariant(QMetaType::fromType<std::nullptr_t>(), nullptr); |
626 | if (d->isBoolean()) |
627 | return QVariant(d->booleanValue()); |
628 | if (d->isInteger()) |
629 | return QVariant(d->integerValue()); |
630 | if (d->isNumber()) |
631 | return QVariant(d->doubleValue()); |
632 | if (d->isString()) |
633 | return QVariant(d->toQString()); |
634 | if (d->as<QV4::Managed>()) |
635 | return QV4::ExecutionEngine::toVariant(value: *d, typeHint: QMetaType{}, createJSValueForObjectsAndSymbols: true); |
636 | |
637 | Q_UNREACHABLE_RETURN(QVariant()); |
638 | } |
639 | |
640 | /*! |
641 | * If this QJSManagedValue holds a JavaScript regular expression object, returns |
642 | * an equivalent QRegularExpression. Otherwise returns an invalid one. |
643 | */ |
644 | QRegularExpression QJSManagedValue::toRegularExpression() const |
645 | { |
646 | if (const auto *r = d ? d->as<QV4::RegExpObject>() : nullptr) |
647 | return r->toQRegularExpression(); |
648 | return {}; |
649 | } |
650 | |
651 | /*! |
652 | * If this QJSManagedValue holds a JavaScript Url object, returns |
653 | * an equivalent QUrl. Otherwise returns an invalid one. |
654 | */ |
655 | QUrl QJSManagedValue::toUrl() const |
656 | { |
657 | if (const auto *u = d ? d->as<QV4::UrlObject>() : nullptr) |
658 | return u->toQUrl(); |
659 | return {}; |
660 | } |
661 | |
662 | /*! |
663 | * If this QJSManagedValue holds a QObject pointer, returns it. Otherwise |
664 | * returns nullptr. |
665 | */ |
666 | QObject *QJSManagedValue::toQObject() const |
667 | { |
668 | if (const auto *o = d ? d->as<QV4::QObjectWrapper>() : nullptr) |
669 | return o->object(); |
670 | return {}; |
671 | } |
672 | |
673 | /*! |
674 | * If this QJSManagedValue holds a QMetaObject pointer, returns it. |
675 | * Otherwise returns nullptr. |
676 | */ |
677 | const QMetaObject *QJSManagedValue::toQMetaObject() const |
678 | { |
679 | if (const auto *m = d ? d->as<QV4::QMetaObjectWrapper>() : nullptr) |
680 | return m->metaObject(); |
681 | return {}; |
682 | } |
683 | |
684 | /*! |
685 | * If this QJSManagedValue holds a JavaScript Date object, returns an equivalent |
686 | * QDateTime. Otherwise returns an invalid one. |
687 | */ |
688 | QDateTime QJSManagedValue::toDateTime() const |
689 | { |
690 | if (const auto *t = d ? d->as<QV4::DateObject>() : nullptr) |
691 | return t->toQDateTime(); |
692 | return {}; |
693 | } |
694 | |
695 | /*! |
696 | * Returns \c true if this QJSManagedValue has a property \a name, otherwise |
697 | * returns \c false. The properties of the prototype chain are considered. |
698 | */ |
699 | bool QJSManagedValue::hasProperty(const QString &name) const |
700 | { |
701 | if (!d || d->isNullOrUndefined()) |
702 | return false; |
703 | |
704 | if (d->isString() && name == QStringLiteral("length" )) |
705 | return true; |
706 | |
707 | if (QV4::Object *obj = d->as<QV4::Object>()) { |
708 | QV4::Scope scope(obj->engine()); |
709 | QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(s: name)); |
710 | return obj->hasProperty(id: key); |
711 | } |
712 | |
713 | return prototype().hasProperty(name); |
714 | } |
715 | |
716 | /*! |
717 | * Returns \c true if this QJSManagedValue has a property \a name, otherwise |
718 | * returns \c false. The properties of the prototype chain are not considered. |
719 | */ |
720 | bool QJSManagedValue::hasOwnProperty(const QString &name) const |
721 | { |
722 | if (!d || d->isNullOrUndefined()) |
723 | return false; |
724 | |
725 | if (d->isString() && name == QStringLiteral("length" )) |
726 | return true; |
727 | |
728 | if (QV4::Object *obj = d->as<QV4::Object>()) { |
729 | QV4::Scope scope(obj->engine()); |
730 | QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(s: name)); |
731 | return obj->getOwnProperty(id: key) != QV4::Attr_Invalid; |
732 | } |
733 | |
734 | return false; |
735 | } |
736 | |
737 | /*! |
738 | * Returns the property \a name of this QJSManagedValue. The prototype chain |
739 | * is searched if the property is not found on the actual object. |
740 | */ |
741 | QJSValue QJSManagedValue::property(const QString &name) const |
742 | { |
743 | if (!d) |
744 | return QJSValue(); |
745 | |
746 | if (d->isNullOrUndefined()) { |
747 | QV4::ExecutionEngine *e = v4Engine(d); |
748 | e->throwTypeError(QStringLiteral("Cannot read property '%1' of null" ).arg(a: name)); |
749 | return QJSValue(); |
750 | } |
751 | |
752 | if (QV4::String *string = d->as<QV4::String>()) { |
753 | if (name == QStringLiteral("length" )) |
754 | return QJSValue(string->d()->length()); |
755 | } |
756 | |
757 | if (QV4::Object *obj = d->as<QV4::Object>()) { |
758 | QV4::Scope scope(obj->engine()); |
759 | QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(s: name)); |
760 | return QJSValuePrivate::fromReturnedValue(d: obj->get(id: key)); |
761 | } |
762 | |
763 | return prototype().property(name); |
764 | } |
765 | |
766 | /*! |
767 | * Sets the property \a name to \a value on this QJSManagedValue. This can only |
768 | * be done on JavaScript values of type \c object. Furhermore, \a value has to be |
769 | * either a primitive or belong to the same engine as this value. |
770 | */ |
771 | void QJSManagedValue::setProperty(const QString &name, const QJSValue &value) |
772 | { |
773 | if (!d) |
774 | return; |
775 | |
776 | if (d->isNullOrUndefined()) { |
777 | v4Engine(d)->throwTypeError( |
778 | QStringLiteral("Value is null and could not be converted to an object" )); |
779 | } |
780 | |
781 | if (QV4::Object *obj = d->as<QV4::Object>()) { |
782 | QV4::Scope scope(obj->engine()); |
783 | QV4::ExecutionEngine *v4 = QJSValuePrivate::engine(jsval: &value); |
784 | if (Q_UNLIKELY(v4 && v4 != scope.engine)) { |
785 | qWarning(msg: "QJSManagedValue::setProperty() failed: " |
786 | "Value was created in different engine." ); |
787 | return; |
788 | } |
789 | QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(s: name)); |
790 | obj->put(id: key, v: QJSValuePrivate::convertToReturnedValue(e: scope.engine, jsval: value)); |
791 | } |
792 | } |
793 | |
794 | /*! |
795 | * Deletes the property \a name from this QJSManagedValue. Returns \c true if |
796 | * the deletion succeeded, or \c false otherwise. |
797 | */ |
798 | bool QJSManagedValue::deleteProperty(const QString &name) |
799 | { |
800 | if (!d) |
801 | return false; |
802 | |
803 | if (QV4::Object *obj = d->as<QV4::Object>()) { |
804 | QV4::Scope scope(obj->engine()); |
805 | QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(s: name)); |
806 | return obj->deleteProperty(id: key); |
807 | } |
808 | |
809 | return false; |
810 | } |
811 | |
812 | /*! |
813 | * Returns \c true if this QJSManagedValue has an array index \a arrayIndex, |
814 | * otherwise returns \c false. The properties of the prototype chain are |
815 | * considered. |
816 | */ |
817 | bool QJSManagedValue::hasProperty(quint32 arrayIndex) const |
818 | { |
819 | if (!d || d->isNullOrUndefined()) |
820 | return false; |
821 | |
822 | if (QV4::String *string = d->as<QV4::String>()) |
823 | return arrayIndex < quint32(string->d()->length()); |
824 | |
825 | if (QV4::Object *obj = d->as<QV4::Object>()) { |
826 | bool hasProperty = false; |
827 | if (arrayIndex == std::numeric_limits<quint32>::max()) |
828 | obj->get(name: obj->engine()->id_uintMax(), hasProperty: &hasProperty); |
829 | else |
830 | obj->get(idx: arrayIndex, hasProperty: &hasProperty); |
831 | return hasProperty; |
832 | } |
833 | |
834 | return prototype().hasProperty(arrayIndex); |
835 | } |
836 | |
837 | /*! |
838 | * Returns \c true if this QJSManagedValue has an array index \a arrayIndex, |
839 | * otherwise returns \c false. The properties of the prototype chain are not |
840 | * considered. |
841 | */ |
842 | bool QJSManagedValue::hasOwnProperty(quint32 arrayIndex) const |
843 | { |
844 | if (!d || d->isNullOrUndefined()) |
845 | return false; |
846 | |
847 | if (QV4::String *string = d->as<QV4::String>()) |
848 | return arrayIndex < quint32(string->d()->length()); |
849 | |
850 | if (QV4::Object *obj = d->as<QV4::Object>()) { |
851 | if (arrayIndex == std::numeric_limits<quint32>::max()) { |
852 | return obj->getOwnProperty(id: obj->engine()->id_uintMax()->toPropertyKey()) |
853 | != QV4::Attr_Invalid; |
854 | } else { |
855 | return obj->getOwnProperty(id: QV4::PropertyKey::fromArrayIndex(idx: arrayIndex)) |
856 | != QV4::Attr_Invalid; |
857 | } |
858 | } |
859 | |
860 | return false; |
861 | } |
862 | |
863 | /*! |
864 | * Returns the property stored at \a arrayIndex of this QJSManagedValue. The |
865 | * prototype chain is searched if the property is not found on the actual |
866 | * object. |
867 | */ |
868 | QJSValue QJSManagedValue::property(quint32 arrayIndex) const |
869 | { |
870 | if (!d || d->isNullOrUndefined()) |
871 | return QJSValue(); |
872 | |
873 | if (QV4::String *string = d->as<QV4::String>()) { |
874 | const QString qString = string->toQString(); |
875 | if (arrayIndex < quint32(qString.size())) |
876 | return qString.sliced(pos: arrayIndex, n: 1); |
877 | return QJSValue(); |
878 | } |
879 | |
880 | if (QV4::Object *obj = d->as<QV4::Object>()) { |
881 | if (arrayIndex == std::numeric_limits<quint32>::max()) |
882 | return QJSValuePrivate::fromReturnedValue(d: obj->get(name: obj->engine()->id_uintMax())); |
883 | else |
884 | return QJSValuePrivate::fromReturnedValue(d: obj->get(idx: arrayIndex)); |
885 | } |
886 | |
887 | return prototype().property(arrayIndex); |
888 | } |
889 | |
890 | /*! |
891 | * Stores the \a value at \a arrayIndex in this QJSManagedValue. This can only |
892 | * be done on JavaScript values of type \c object, and it's not recommended if the |
893 | * value is not an array. Furhermore, \a value has to be either a primitive or |
894 | * belong to the same engine as this value. |
895 | */ |
896 | void QJSManagedValue::setProperty(quint32 arrayIndex, const QJSValue &value) |
897 | { |
898 | if (!d) |
899 | return; |
900 | |
901 | if (QV4::Object *obj = d->as<QV4::Object>()) { |
902 | QV4::ExecutionEngine *v4 = QJSValuePrivate::engine(jsval: &value); |
903 | if (Q_UNLIKELY(v4 && v4 != obj->engine())) { |
904 | qWarning(msg: "QJSManagedValue::setProperty() failed: " |
905 | "Value was created in different engine." ); |
906 | return; |
907 | } |
908 | obj->put(idx: arrayIndex, v: QJSValuePrivate::convertToReturnedValue(e: v4, jsval: value)); |
909 | } |
910 | } |
911 | |
912 | /*! |
913 | * Deletes the value stored at \a arrayIndex from this QJSManagedValue. Returns |
914 | * \c true if the deletion succeeded, or \c false otherwise. |
915 | */ |
916 | bool QJSManagedValue::deleteProperty(quint32 arrayIndex) |
917 | { |
918 | if (!d) |
919 | return false; |
920 | |
921 | if (QV4::Object *obj = d->as<QV4::Object>()) |
922 | return obj->deleteProperty(id: QV4::PropertyKey::fromArrayIndex(idx: arrayIndex)); |
923 | |
924 | return false; |
925 | } |
926 | |
927 | static const QV4::FunctionObject *functionObjectForCall(QV4::Value *d) |
928 | { |
929 | if (Q_UNLIKELY(!d)) { |
930 | qWarning(msg: "QJSManagedValue: Calling a default-constructed or moved-from managed value" |
931 | "should throw an exception, but there is no engine to receive it." ); |
932 | return nullptr; |
933 | } |
934 | |
935 | if (const QV4::FunctionObject *f = d->as<QV4::FunctionObject>()) |
936 | return f; |
937 | |
938 | v4Engine(d)->throwTypeError(QStringLiteral("Value is not a function" )); |
939 | return nullptr; |
940 | } |
941 | |
942 | /*! |
943 | * If this QJSManagedValue represents a JavaScript FunctionObject, calls it with |
944 | * the given \a arguments, and returns the result. Otherwise returns a |
945 | * JavaScript \c undefined value. |
946 | * |
947 | * The \a arguments have to be either primitive values or belong to the same |
948 | * QJSEngine as this QJSManagedValue. Otherwise the call is not carried |
949 | * out and a JavaScript \c undefined value is returned. |
950 | */ |
951 | QJSValue QJSManagedValue::call(const QJSValueList &arguments) const |
952 | { |
953 | const QV4::FunctionObject *f = functionObjectForCall(d); |
954 | if (!f) |
955 | return QJSValue(); |
956 | |
957 | QV4::ExecutionEngine *engine = f->engine(); |
958 | |
959 | QV4::Scope scope(engine); |
960 | QV4::JSCallArguments jsCallData(scope, arguments.size()); |
961 | *jsCallData.thisObject = engine->globalObject; |
962 | int i = 0; |
963 | for (const QJSValue &arg : arguments) { |
964 | if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) { |
965 | qWarning(msg: "QJSManagedValue::call() failed: Argument was created in different engine." ); |
966 | return QJSValue(); |
967 | } |
968 | jsCallData.args[i++] = QJSValuePrivate::convertToReturnedValue(e: engine, jsval: arg); |
969 | } |
970 | |
971 | return QJSValuePrivate::fromReturnedValue(d: f->call(data: jsCallData)); |
972 | } |
973 | |
974 | /*! |
975 | * If this QJSManagedValue represents a JavaScript FunctionObject, calls it on |
976 | * \a instance with the given \a arguments, and returns the result. Otherwise |
977 | * returns a JavaScript \c undefined value. |
978 | * |
979 | * The \a arguments and the \a instance have to be either primitive values or |
980 | * belong to the same QJSEngine as this QJSManagedValue. Otherwise the call is |
981 | * not carried out and a JavaScript \c undefined value is returned. |
982 | */ |
983 | QJSValue QJSManagedValue::callWithInstance(const QJSValue &instance, |
984 | const QJSValueList &arguments) const |
985 | { |
986 | const QV4::FunctionObject *f = functionObjectForCall(d); |
987 | if (!f) |
988 | return QJSValue(); |
989 | |
990 | QV4::ExecutionEngine *engine = f->engine(); |
991 | |
992 | if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, instance))) { |
993 | qWarning(msg: "QJSManagedValue::callWithInstance() failed: " |
994 | "Instance was created in different engine." ); |
995 | return QJSValue(); |
996 | } |
997 | |
998 | QV4::Scope scope(engine); |
999 | QV4::JSCallArguments jsCallData(scope, arguments.size()); |
1000 | *jsCallData.thisObject = QJSValuePrivate::convertToReturnedValue(e: engine, jsval: instance); |
1001 | int i = 0; |
1002 | for (const QJSValue &arg : arguments) { |
1003 | if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) { |
1004 | qWarning(msg: "QJSManagedValue::callWithInstance() failed: " |
1005 | "Argument was created in different engine." ); |
1006 | return QJSValue(); |
1007 | } |
1008 | jsCallData.args[i++] = QJSValuePrivate::convertToReturnedValue(e: engine, jsval: arg); |
1009 | } |
1010 | |
1011 | return QJSValuePrivate::fromReturnedValue(d: f->call(data: jsCallData)); |
1012 | } |
1013 | |
1014 | /*! |
1015 | * If this QJSManagedValue represents a JavaScript FunctionObject, calls it as |
1016 | * constructor with the given \a arguments, and returns the result. Otherwise |
1017 | * returns a JavaScript \c undefined value. |
1018 | * |
1019 | * The \a arguments have to be either primitive values or belong to the same |
1020 | * QJSEngine as this QJSManagedValue. Otherwise the call is not carried |
1021 | * out and a JavaScript \c undefined value is returned. |
1022 | */ |
1023 | QJSValue QJSManagedValue::callAsConstructor(const QJSValueList &arguments) const |
1024 | { |
1025 | const QV4::FunctionObject *f = functionObjectForCall(d); |
1026 | if (!f) |
1027 | return QJSValue(); |
1028 | |
1029 | QV4::ExecutionEngine *engine = f->engine(); |
1030 | |
1031 | QV4::Scope scope(engine); |
1032 | QV4::JSCallArguments jsCallData(scope, arguments.size()); |
1033 | int i = 0; |
1034 | for (const QJSValue &arg : arguments) { |
1035 | if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) { |
1036 | qWarning(msg: "QJSManagedValue::callAsConstructor() failed: " |
1037 | "Argument was created in different engine." ); |
1038 | return QJSValue(); |
1039 | } |
1040 | jsCallData.args[i++] = QJSValuePrivate::convertToReturnedValue(e: engine, jsval: arg); |
1041 | } |
1042 | |
1043 | return QJSValuePrivate::fromReturnedValue(d: f->callAsConstructor(data: jsCallData)); |
1044 | } |
1045 | |
1046 | /*! |
1047 | * \internal |
1048 | * |
1049 | * Retrieves the JavaScript meta type of this value. The JavaScript meta type |
1050 | * represents the layout of members in an object. Instantiating a meta type is |
1051 | * faster than re-constructing the same object using a sequence of setProperty() |
1052 | * calls on a new object. |
1053 | * |
1054 | * \sa members(), instantiate() |
1055 | */ |
1056 | QJSManagedValue QJSManagedValue::jsMetaType() const |
1057 | { |
1058 | if (!d) |
1059 | return QJSManagedValue(); |
1060 | |
1061 | QJSManagedValue result(v4Engine(d)); |
1062 | if (QV4::Managed *m = d->as<QV4::Managed>()) |
1063 | *result.d = m->internalClass(); |
1064 | |
1065 | return result; |
1066 | } |
1067 | |
1068 | /*! |
1069 | * \internal |
1070 | * |
1071 | * If this value is a JavaScript meta type, retrieves the names of its members |
1072 | * The ordering of the names corresponds to the ordering of the values to be |
1073 | * passed to instantiate(). |
1074 | * |
1075 | * If the value is not a meta type, an empty list is returned. |
1076 | * |
1077 | * \sa isMetaType(), metaType(), instantiate() |
1078 | */ |
1079 | QStringList QJSManagedValue::jsMetaMembers() const |
1080 | { |
1081 | if (!d) |
1082 | return {}; |
1083 | |
1084 | if (QV4::InternalClass *c = d->as<QV4::InternalClass>()) { |
1085 | const auto heapClass = c->d(); |
1086 | const int size = heapClass->size; |
1087 | QStringList result; |
1088 | result.reserve(asize: size); |
1089 | for (int i = 0; i < size; ++i) |
1090 | result.append(t: heapClass->keyAt(index: i)); |
1091 | return result; |
1092 | } |
1093 | |
1094 | return {}; |
1095 | } |
1096 | |
1097 | /*! |
1098 | * \internal |
1099 | * |
1100 | * If this value is a JavaScript meta type, instantiates it using the |
1101 | * \a values, and returns the result. Otherwise returns undefined. |
1102 | * |
1103 | * The values are expected in the same order as the keys in the return value of |
1104 | * members(), and that is the order in which properties were added to the object |
1105 | * this meta type originally belongs to. |
1106 | * |
1107 | * \sa members(), metaType(), isMetaType(). |
1108 | */ |
1109 | QJSManagedValue QJSManagedValue::jsMetaInstantiate(const QJSValueList &values) const |
1110 | { |
1111 | if (!d) |
1112 | return {}; |
1113 | |
1114 | if (QV4::InternalClass *c = d->as<QV4::InternalClass>()) { |
1115 | QV4::ExecutionEngine *engine = c->engine(); |
1116 | QJSManagedValue result(engine); |
1117 | *result.d = c->engine()->newObject(internalClass: c->d()); |
1118 | QV4::Object *o = result.d->as<QV4::Object>(); |
1119 | |
1120 | for (uint i = 0, end = qMin(a: qsizetype(c->d()->size), b: values.size()); i < end; ++i) { |
1121 | const QJSValue &arg = values[i]; |
1122 | if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) { |
1123 | qWarning(msg: "QJSManagedValue::instantiate() failed: " |
1124 | "Argument was created in different engine." ); |
1125 | return QJSManagedValue(); |
1126 | } |
1127 | o->setProperty(index: i, v: QJSValuePrivate::convertToReturnedValue(e: engine, jsval: arg)); |
1128 | } |
1129 | |
1130 | return result; |
1131 | } |
1132 | |
1133 | return {}; |
1134 | } |
1135 | |
1136 | QJSManagedValue::QJSManagedValue(QV4::ExecutionEngine *engine) : |
1137 | d(engine->memoryManager->m_persistentValues->allocate()) |
1138 | { |
1139 | } |
1140 | |
1141 | QT_END_NAMESPACE |
1142 | |