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