• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# JSVM-API Development Specifications
2
3## Lifecycle Management
4
5**[Rule]** Properly use **OH_JSVM_OpenHandleScope** and **OH_JSVM_CloseHandleScope** to minimize the lifecycle of **JSVM_Value** and prevent memory leaks.
6
7Each **JSVM_Value** belongs to a specific **HandleScope** instance, which is created by **OH_JSVM_OpenHandleScope** and closed by **OH_JSVM_CloseHandleScope**. After a **HandleScope** instance is closed, the corresponding **JSVM_Value** will be automatically released.
8
9> **NOTE**
10>
11> - **JSVM_Value** can be created only after **HandleScope** is opened; otherwise, the application may crash. Node-API does not have this restriction.
12> - **JSVM_Value** cannot be used after the corresponding **HandleScope** is closed. To hold **JSVM_Value** persistently, call **OH_JSVM_CreateReference** to convert **JSVM_Value** to **JSVM_Ref**.
13> - The scopes (including **JSVM_VMScope**, **JSVM_EnvScope**, and **JSVM_HandleScope**) must be closed in reverse order. The scope opened first must be closed last. Otherwise, the application may crash.
14
15**Example (scope closing error)**
16```
17// If JSVM_VMScope is not closed in reverse order, the application may crash.
18JSVM_VM vm;
19JSVM_CreateVMOptions options;
20OH_JSVM_CreateVM(&options, &vm);
21
22JSVM_VMScope vmScope1, vmScope2;
23OH_JSVM_OpenVMScope(vm, &vmScope1);
24OH_JSVM_OpenVMScope(vm, &vmScope2);
25
26// You need to close vmScope2 and then vmScope1.
27OH_JSVM_CloseVMScope(vm, vmScope1);
28OH_JSVM_CloseVMScope(vm, vmScope2);
29OH_JSVM_DestroyVM(vm);
30```
31
32
33**Encapsulation in C++**
34
35```
36class HandleScopeWrapper {
37 public:
38  HandleScopeWrapper(JSVM_Env env) : env(env) {
39    OH_JSVM_OpenHandleScope(env, &handleScope);
40  }
41
42  ~HandleScopeWrapper() {
43    OH_JSVM_CloseHandleScope(env, handleScope);
44  }
45
46  HandleScopeWrapper(const HandleScopeWrapper&) = delete;
47  HandleScopeWrapper& operator=(const HandleScopeWrapper&) = delete;
48  HandleScopeWrapper(HandleScopeWrapper&&) = delete;
49  void* operator new(size_t) = delete;
50  void* operator new[](size_t) = delete;
51
52 protected:
53  JSVM_Env env;
54  JSVM_HandleScope handleScope;
55};
56```
57
58**Example**
59
60```c++
61// When JSVM-API is frequently called to create JS objects in the for loop, use handle_scope to release resources in a timely manner when they are no longer used.
62// In the following example, the lifecycle of the local variable res ends at the end of each loop. To prevent memory leaks, scope is used to release the JS object in a timely manner.
63// After each for loop ends, trigger the destructor function of HandleScopeWrapper to release the JS object held by scope.
64for (int i = 0; i < 100000; i++)
65{
66    HandleScopeWrapper scope(env);
67    JSVM_Value res;
68    OH_JSVM_CreateObject(env, &res);
69    if (i == 1000) {
70        // After the loop exits, the HandleScopeWrapper destructor is automatically called to release resources.
71        break;
72    }
73}
74```
75
76## Context Sensitive in Multiple JSVM Instances
77
78**[Rule]** Do not use JSVM-API to access JS objects across JSVM instances.
79
80A JSVM instance is an independent running environment. Operations such as creating and accessing a JS object must be performed in the same JSVM instance. If an object is operated in different JSVM instances, the application may crash. A JSVM instance is represented as a **JSVM_Env** in APIs.
81
82**Example (incorrect)**
83
84```c++
85// Create a string object with value of "bar" in env1.
86OH_JSVM_CreateStringUtf8(env1, "value1", JSVM_AUTO_LENGTH , &string);
87// Create an object in env2 and set the string object to this object.
88JSVM_Status status = OH_JSVM_CreateObject(env2, &object);
89if (status != JSVM_OK)
90{
91    return;
92}
93
94status = OH_JSVM_SetNamedProperty(env2, object, "string1", string);
95if (status != JSVM_OK)
96{
97    return;
98}
99```
100
101A JS object belongs to a specific **JSVM_Env**. Therefore, you cannot set an object of env1 to an object of env2. If the object of env1 is accessed in env2, the application may crash.
102
103## JSVM Instance Shared by Multiple Threads
104
105[**Rule**] When multiple threads use the same JSVM instance, use a lock to ensure that the JSVM instance can be executed in only one thread at a time. If multiple threads use the JSVM instance at the same time, the application may crash.
106
107> **NOTE**
108
109> - You can use **OH_JSVM_IsLocked** to check whether the calling thread holds the lock of the JSVM instance instead of setting a loop to wait for other threads to release the lock.
110> - Nested use of **OH_JSVM_AcquireLock** in the same thread will not cause deadlock.
111> - When using **OH_JSVM_ReleaseLock**, you need to check whether it is at the outermost layer to prevent the inner layer from releasing the lock of the entire thread when **OH_JSVM_AcquireLock** is nested in the same thread.
112> - After **OH_JSVM_AcquireLock** is called, use **OH_JSVM_OpenHandleScope** to enable the JSVM instance to enter the thread. After **OH_JSVM_ReleaseLock** is called, use **OH_JSVM_ReleaseLock** to enable the JSVM instance to exit the thread.
113> - A JSVM instance cannot be nested across threads. If you need to temporarily change the thread that uses the JSVM instance, ensure that **JSVM_Value** is saved as **JSVM_Ref**. After the lock is released, **JSVM_Value** cannot be accessed.
114> - The sequence of obtaining resources is as follows: Lock -> VMScope -> EnvScope -> HandleScope. The sequence of releasing resources is the opposite. An incorrect sequence may cause the application to crash.
115
116**Encapsulation in C++**
117
118```
119class LockWrapper {
120 public:
121  // Constructor, lock acquisition, VMScope, and EnvScope.
122  LockWrapper(JSVM_Env env) : env(env) {
123    OH_JSVM_IsLocked(env, &isLocked);
124    if (!isLocked) {
125      OH_JSVM_AcquireLock(env);
126      OH_JSVM_GetVM(env, &vm);
127      OH_JSVM_OpenVMScope(vm, &vmScope);
128      OH_JSVM_OpenEnvScope(env, &envScope);
129    }
130  }
131
132  // Destructor used to release EnvScope, VMScope, and lock.
133  ~LockWrapper() {
134    if (!isLocked) {
135      OH_JSVM_CloseEnvScope(env, envScope);
136      OH_JSVM_CloseVMScope(vm, vmScope);
137      OH_JSVM_ReleaseLock(env);
138    }
139  }
140
141  LockWrapper(const LockWrapper&) = delete;
142  LockWrapper& operator=(const LockWrapper&) = delete;
143  LockWrapper(LockWrapper&&) = delete;
144  void* operator new(size_t) = delete;
145  void* operator new[](size_t) = delete;
146
147 private:
148  JSVM_Env env;
149  JSVM_EnvScope envScope;
150  JSVM_VMScope vmScope;
151  JSVM_VM vm;
152  bool isLocked;
153};
154```
155
156
157
158**Example (correct)**
159
160```
161// This example demonstrates how to use a JSVM across threads.
162// Thread t1 obtains the lock and call JSVM-API.
163// Thread t2 is blocked when thread t1 obtained the lock, continues to call JSVM-API when t1 releases the lock.
164static napi_value Add([[maybe_unused]] napi_env _env, [[maybe_unused]] napi_callback_info _info) {
165    static JSVM_VM vm;
166    static JSVM_Env env;
167    if (aa == 0) {
168        OH_JSVM_Init(nullptr);
169        aa++;
170        // create vm
171        JSVM_CreateVMOptions options;
172        memset(&options, 0, sizeof(options));
173        OH_JSVM_CreateVM(&options, &vm);
174        // Create env.
175        OH_JSVM_CreateEnv(vm, 0, nullptr, &env);
176    }
177
178    std::thread t1([]() {
179        LockWrapper lock(env);
180        JSVM_HandleScope handleScope;
181        OH_JSVM_OpenHandleScope(env, &handleScope);
182        JSVM_Value value;
183        JSVM_Status rst = OH_JSVM_CreateInt32(env, 32, &value); // 32: numerical value
184        if (rst == JSVM_OK) {
185            OH_LOG_INFO(LOG_APP, "JSVM:t1 OH_JSVM_CreateInt32 suc");
186        } else {
187            OH_LOG_ERROR(LOG_APP, "JSVM:t1 OH_JSVM_CreateInt32 fail");
188        }
189        int32_t num1;
190        OH_JSVM_GetValueInt32(env, value, &num1);
191        OH_LOG_INFO(LOG_APP, "JSVM:t1 num1 = %{public}d", num1);
192        OH_JSVM_CloseHandleScope(env, handleScope);
193    });
194    std::thread t2([]() {
195        LockWrapper lock(env);
196        JSVM_HandleScope handleScope;
197        OH_JSVM_OpenHandleScope(env, &handleScope);
198        JSVM_Value value;
199        JSVM_Status rst = OH_JSVM_CreateInt32(env, 32, &value); // 32: numerical value
200        if (rst == JSVM_OK) {
201            OH_LOG_INFO(LOG_APP, "JSVM:t2 OH_JSVM_CreateInt32 suc");
202        } else {
203            OH_LOG_ERROR(LOG_APP, "JSVM:t2 OH_JSVM_CreateInt32 fail");
204        }
205        int32_t num1;
206        OH_JSVM_GetValueInt32(env, value, &num1);
207        OH_LOG_INFO(LOG_APP, "JSVM:t2 num1 = %{public}d", num1);
208        OH_JSVM_CloseHandleScope(env, handleScope);
209    });
210    t1.detach();
211    t2.detach();
212    return nullptr;
213}
214```
215
216## Obtaining Arguments Passed from JS
217
218**[Rule]** When **argv** in **OH_JSVM_GetCbInfo** is not **nullptr**, the length of **argv** must be greater than or equal to **argc**.
219
220If **argv** is not **nullptr**, the arguments actually passed from JS will be written to **argv** in **OH_JSVM_GetCbInfo** based on the value of **argc**. If there are more arguments than the provided count, only the requested number of arguments are copied. If there are fewer arguments provided than the claimed, the rest of **argv** is filled with values that represent **undefined**.
221
222**Example (incorrect)**
223
224```cpp
225static JSVM_Value IncorrectDemo1(JSVM_Env env, JSVM_CallbackInfo info) {
226    // argc is not correctly initialized and is set to a random value. If the length of argv is less than the number of arguments specified by argc, data overwriting occurs.
227    size_t argc;
228    JSVM_Value argv[10] = {nullptr};
229    OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr);
230    return nullptr;
231}
232
233static JSVM_Value IncorrectDemo2(JSVM_Env env, JSVM_CallbackInfo info) {
234    // If the number of arguments specified by argc is greater than the length of argv, out-of-bounds write will occur when data is written to argv in napi_get_cb_info.
235    size_t argc = 5;
236    JSVM_Value argv[3] = {nullptr};
237    OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr);
238    return nullptr;
239}
240```
241
242**Example (correct)**
243
244```cpp
245static JSVM_Value GetArgvDemo1(napi_env env, JSVM_CallbackInfo info) {
246    size_t argc = 0;
247    // Pass in nullptr to argv to obtain the actual number of arguments passed by JS.
248    OH_JSVM_GetCbInfo(env, info, &argc, nullptr, nullptr, nullptr);
249    // If 0 is passed by JS, the subsequent logic is not executed.
250    if (argc == 0) {
251        return nullptr;
252    }
253    // Create an array to obtain the arguments passed by JS.
254    JSVM_Value* argv = new JSVM_Value[argc];
255    OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr);
256    // Service code.
257    // ... ...
258    // argv is an object created by new and must be manually released when it is not required.
259    delete argv;
260    return nullptr;
261}
262
263static JSVM_Value GetArgvDemo2(napi_env env, JSVM_CallbackInfo info) {
264    size_t argc = 2;
265    JSVM_Value* argv[2] = {nullptr};
266    // The arguments (of the quantity specified by argc) passed from JS or undefined will be written to argv of OH_JSVM_GetCbInfo.
267    OH_JSVM_GetCbInfo(env, info, &argc, nullptr, nullptr, nullptr);
268    // Service code.
269    // ... ...
270    return nullptr;
271}
272```
273
274## Exception Handling
275
276**[Suggestion]** Any exception occurred in a JSVM-API call should be handled in a timely manner. Otherwise, unexpected behavior may occur.
277
278 Exception handling can be classified into the following types based on the primary/secondary relationship:
279
2801. If a C++ exception occurs when the JSVM executes a C++ callback function (JS is primary and native is secondary), the exception needs to be thrown to the JSVM. The following example demonstrates the implementation of the C++ callback function in three difference scenarios.
281  >  **NOTE**<br>If the JSVM-API call in a C++ callback fails, you can either throw an exception to the JSVM or not. To throw an exception, ensure that there is no pending exception in the JSVM. If you choose not to throw an exception, JS **try-catch** can capture such JS exceptions. For details, see **NativeFunctionExceptionDemo3**.
282    ```c++
283    // JSVM is primary, and native is secondary.
284    void DoSomething() {
285        throw("Do something failed");
286    }
287
288    // Demo 1: Throw the exception captured in C++ to the JSVM.
289    JSVM_Value NativeFunctionExceptionDemo1(JSVM_Env env, JSVM_CallbackInfo info) {
290        try {
291            DoSomething();
292        } catch (const char *ex) {
293            OH_JSVM_ThrowError(env, nullptr, ex);
294            return nullptr;
295        }
296        return nullptr;
297    }
298
299    // Demo 2: Throw an exception to the JSVM when a JSVM-API call fails.
300    JSVM_Value NativeFunctionExceptionDemo2(JSVM_Env env, JSVM_CallbackInfo info) {
301        JSVM_Value JSBool = nullptr;
302        bool value = false;
303        auto status = OH_JSVM_GetValueBool(env, JSBool, &value);
304        if (status != JSVM_OK) {
305            OH_JSVM_ThrowError(env, nullptr, "Get bool value failed");
306            return nullptr;
307        }
308        return nullptr;
309    }
310
311    // Demo 3: Add an exception to the JSVM for handling when the JSVM-API call fails. In this case, you do not need to throw exceptions to the JSVM.
312    JSVM_Value NativeFunctionExceptionDemo3(JSVM_Env env, JSVM_CallbackInfo info) {
313        std::string sourcecodestr = R"JS(
314            throw Error('Error throw from js');
315        )JS";
316        JSVM_Value sourcecodevalue = nullptr;
317        OH_JSVM_CreateStringUtf8(env, sourcecodestr.c_str(), sourcecodestr.size(), &sourcecodevalue);
318        JSVM_Script script;
319        auto status = OH_JSVM_CompileScript(env, sourcecodevalue, nullptr, 0, true, nullptr, &script);
320        JSVM_Value result;
321        // Execute the JS script and throw a JS exception during the execution.
322        status = OH_JSVM_RunScript(env, script, &result);
323        if (status != JSVM_OK) {
324            bool isPending = false;
325            // If an exception already exists, do not throw the exception to the JSVM.
326            // If a new exception needs to be thrown, handle the pending exception in the JSVM first.
327            if (JSVM_OK == OH_JSVM_IsExceptionPending((env), &isPending) && isPending) {
328                return nullptr;
329            }
330            OH_JSVM_ThrowError(env, nullptr, "Runscript failed");
331            return nullptr;
332        }
333        return nullptr;
334    }
335
336    // Bind NativeFunction to the JSVM. The process is omitted here.
337    std::string sourcecodestr = R"JS(
338        // The console log needs to be implemented.
339        try {
340            // Call the native function.
341            NativeFunction()
342        } catch (e) {
343            // Handle the exception in C/C++.
344            consolelog(e.message)
345        }
346    )JS";
347    JSVM_Value sourcecodevalue = nullptr;
348    OH_JSVM_CreateStringUtf8(env, sourcecodestr.c_str(), sourcecodestr.size(), &sourcecodevalue);
349    JSVM_Script script;
350    auto status = OH_JSVM_CompileScript(env, sourcecodevalue, nullptr, 0, true, nullptr, &script);
351    OH_LOG_INFO(LOG_APP, "JSVM API TEST: %{public}d", (uint32_t)status);
352    JSVM_Value result;
353    // Execute the JS script to call native methods from JS.
354    status = OH_JSVM_RunScript(env, script, &result);
355    ```
356
3572. If JSVM-API (native is primary and JS is secondary) fails to be called from C++, clear the pending exceptions in JSVM to prevent the impact on subsequent JSVM-API execution and set a branch to handling C++ exception (or throw a C++ exception).
358    ```
359    std::string sourcecodestr = R"JS(
360        throw Error('Error throw from js');
361    )JS";
362    JSVM_Value sourcecodevalue = nullptr;
363    OH_JSVM_CreateStringUtf8(env, sourcecodestr.c_str(), sourcecodestr.size(), &sourcecodevalue);
364    JSVM_Script script;
365    auto status = OH_JSVM_CompileScript(env, sourcecodevalue, nullptr, 0, true, nullptr, &script);
366    // Exception handling.
367    if (status != JSVM_OK) {
368        JSVM_Value error = nullptr;
369        // Obtain and clear the exception.
370        CALL_JSVM(OH_JSVM_GetAndClearLastException((env), &error));
371        // Handle the exception, for example, printing information. The code is omitted here.
372        // Throw a C++ exception or stop function execution.
373        throw "JS Compile Error";
374    }
375    JSVM_Value result;
376    // Execute the JS script and throw a JS exception during the execution.
377    status = OH_JSVM_RunScript(env, script, &result);
378
379    // Exception handling.
380    if (status != JSVM_OK) {
381        JSVM_Value error = nullptr;
382        // Obtain and clear the exception.
383        CALL_JSVM(OH_JSVM_GetAndClearLastException((env), &error));
384        // Handle the exception, for example, printing information. The code is omitted here.
385        // Throw a C++ exception or stop function execution.
386        throw "JS RunScript Error";
387    }
388
389    ```
390
391## Binding Object Context
392
393**[Rule]**: The JS function and object generated by JSVM-API can be accessed from JS only after they are bound to the context. The **const char *** parameter in **OH_JSVM_CreateFunction** is the property **name** of the created function, without pointing to the function in the context. This rule also applies to the class and object generated by JSVM-API.
394
395**Example**
396
397```
398JSVM_Value JSFunc = nullptr;
399const char *name = "NativeFunction";
400JSVM_CallbackStruct cb = {NativeFunction, nullptr};
401// Create a JS function, whose property "name" is set to "NativeFunction".
402OH_JSVM_CreateFunction(env, name, strlen(name), &cb, &JSFunc);
403// Bind the function to the context.
404// Obtain the global object of the context.
405JSVM_Value global = nullptr;
406OH_JSVM_GetGlobal(env, &global);
407// Create the JS string "FunctionNameInJSContext."
408JSVM_Value key = nullptr;
409OH_JSVM_CreateStringLatin1(env, "FunctionNameInJSContext", JSVM_AUTO_LENGTH, &key);
410// Set the global property "FunctionNameInJSContext" to "JSFunc". This binds the function to the context.
411OH_JSVM_SetProperty(env, global, key, JSFunc);
412// Call the function from JS.
413std::string sourcecodestr = R"JS(
414    // The console log needs to be implemented.
415    FunctionNameInJSContext() // The call is successful.
416    consolelog(FunctionNameInJSContext.name) // Print "NativeFunction."
417    try {
418    	NativeFunction() // If the function cannot be found, throw an exception.
419    } catch (e) {
420    	consolelog(e.message)
421    }
422)JS";
423```
424