1 /*
2  * Copyright (c) 2020-2021 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "js_page_state_machine.h"
17 #include "ace_log.h"
18 #include "component.h"
19 #include "directive/descriptor_utils.h"
20 #include "fatal_handler.h"
21 #include "js_app_environment.h"
22 #include "js_async_work.h"
23 #include "js_page_state.h"
24 #include "js_profiler.h"
25 #include "jsi.h"
26 #include "lazy_load_manager.h"
27 #include "mem_proc.h"
28 #include "module_manager.h"
29 #include "root_view.h"
30 #include "securec.h"
31 #include "jsi_types.h"
32 #include "jsi/internal/jsi_internal.h"
33 
34 namespace OHOS {
35 namespace ACELite {
StateMachine()36 StateMachine::StateMachine()
37 {
38     currentState_ = UNDEFINED_STATE;
39     for (int i = 0; i < PAGE_STATE_SIZE; i++) {
40         stateMap_[i] = nullptr;
41     }
42     jsPagePath_ = nullptr;
43     appRootPath_ = nullptr;
44     appContext_ = nullptr;
45     uri_ = nullptr;
46     rootComponent_ = nullptr;
47     viewModel_ = UNDEFINED;
48     object_ = UNDEFINED;
49     hasParams_ = false;
50     isEntireHidden_ = false;
51     watchersHead_ = nullptr;
52     scrollLayer_ = nullptr;
53 }
54 
~StateMachine()55 StateMachine::~StateMachine()
56 {
57     // release this page's all resource
58     // if error happens, statemachine must force to jump to destroy state for releasing resource.
59     if ((currentState_ >= INIT_STATE) || FatalHandler::GetInstance().IsFatalErrorHitted()) {
60         ChangeState(BACKGROUND_STATE);
61         ChangeState(DESTROY_STATE);
62     }
63     for (int i = 0; i < PAGE_STATE_SIZE; i++) {
64         if (stateMap_[i] != nullptr) {
65             delete stateMap_[i];
66             stateMap_[i] = nullptr;
67         }
68     }
69     if (uri_ != nullptr) {
70         ace_free(uri_);
71         uri_ = nullptr;
72     }
73     if (jsPagePath_ != nullptr) {
74         ace_free(jsPagePath_);
75         jsPagePath_ = nullptr;
76     }
77     if (!jerry_value_is_undefined(object_)) {
78         jerry_release_value(object_);
79     }
80 }
81 
SetCurrentState(int8_t newState)82 void StateMachine::SetCurrentState(int8_t newState)
83 {
84     if (newState <= UNDEFINED_STATE || newState >= END_STATE) {
85         HILOG_ERROR(HILOG_MODULE_ACE, "error input state:%{public}d", newState);
86         return;
87     }
88     currentState_ = newState;
89 }
90 
GenerateJsPagePath(const char * const uri)91 int StateMachine::GenerateJsPagePath(const char * const uri)
92 {
93     size_t uriLen = strlen(uri);
94     if (uriLen >= PATH_LENGTH_MAX) {
95         return ERROR_INPUT_PARAMETER;
96     }
97     size_t len = uriLen;
98     // if path is "/", load default page of app.(appRootPath/index/index.js)
99 #if JS_PAGE_SPECIFIC
100     if (jsPageSpecific.jsIndexFilePath == nullptr) {
101         jsPageSpecific.jsIndexFilePath = const_cast<char *>(JS_INDEX_FILE_PATH);
102     }
103     len = strlen(jsPageSpecific.jsIndexFilePath);
104     if (len < uriLen) {
105         len = uriLen;
106     }
107 #else
108     if ((uriLen == 1) && (uri[0] == PATH_DEFAULT[0])) {
109         len = strlen(JS_INDEX_FILE_PATH);
110     }
111 #endif
112     const char * const sourceFileSuffix = (JsAppEnvironment::GetInstance()->IsSnapshotMode()) ? ".bc" : ".js";
113     len += strlen(sourceFileSuffix);
114     // add ending character:'\0'
115     len += 1;
116     jsPagePath_ = static_cast<char *>(ace_malloc(len));
117     if (jsPagePath_ == nullptr) {
118         HILOG_ERROR(HILOG_MODULE_ACE, "malloc path memory heap failed.");
119         return ERROR_MALLOC;
120     }
121     jsPagePath_[0] = '\0';
122     errno_t err = 0;
123     if ((uriLen == 1) && (uri[0] == PATH_DEFAULT[0])) {
124 #if JS_PAGE_SPECIFIC
125         err = strcpy_s(jsPagePath_, len, jsPageSpecific.jsIndexFilePath);
126 #else
127         err = strcpy_s(jsPagePath_, len, JS_INDEX_FILE_PATH);
128 #endif
129     } else {
130         err = strcpy_s(jsPagePath_, len, uri);
131     }
132     JS_PAGE_RETURN_IF_ERROR(err, jsPagePath_);
133     err = strcat_s(jsPagePath_, len, sourceFileSuffix);
134     JS_PAGE_RETURN_IF_ERROR(err, jsPagePath_);
135     return SUCCESS;
136 }
137 
RegisterUriAndParamsToPage(const char * const uri,jerry_value_t params)138 void StateMachine::RegisterUriAndParamsToPage(const char * const uri, jerry_value_t params)
139 {
140     jerry_value_t globalObject = jerry_get_global_object();
141     if (!IS_UNDEFINED(params)) {
142         // add $page.path property
143         JerrySetStringProperty(params, ROUTER_PAGE_PATH, uri);
144         JerrySetNamedProperty(globalObject, ROUTER_PAGE, params);
145         hasParams_ = true;
146     } else {
147         jerry_value_t pagePropName = jerry_create_string(reinterpret_cast<const jerry_char_t *>(ROUTER_PAGE));
148         bool success = jerry_delete_property(globalObject, pagePropName);
149         if (!success) {
150             HILOG_ERROR(HILOG_MODULE_ACE, "delete $page property failed from global object.");
151         }
152         jerry_release_value(pagePropName);
153     }
154     jerry_release_value(globalObject);
155 }
156 
Init(jerry_value_t object,jerry_value_t & jsRes)157 bool StateMachine::Init(jerry_value_t object, jerry_value_t &jsRes)
158 {
159     appContext_ = JsAppContext::GetInstance();
160     appRootPath_ = const_cast<char *>(appContext_->GetCurrentAbilityPath());
161     if ((appRootPath_ == nullptr) || (strlen(appRootPath_) == 0)) {
162         HILOG_ERROR(HILOG_MODULE_ACE, "statemachine init failed as this app's root path is invalid.");
163         return false;
164     }
165     // create new references to object's value,otherwise jerry engine will crash.
166     object_ = jerry_acquire_value(object);
167     stateMap_[INIT_STATE] = new PageInitState();
168     stateMap_[READY_STATE] = new PageReadyState();
169     stateMap_[SHOW_STATE] = new PageShowState();
170     stateMap_[BACKGROUND_STATE] = new PageBackgroundState();
171     stateMap_[DESTROY_STATE] = new PageDestroyState();
172     return BindUri(jsRes);
173 }
174 
BindUri(jerry_value_t & jsRes)175 bool StateMachine::BindUri(jerry_value_t &jsRes)
176 {
177     // check1:object undefined or type error
178     if (!jerry_value_is_object(object_)) {
179         HILOG_ERROR(HILOG_MODULE_ACE, "statemachine init failed as object is invalid.");
180         jsRes = jerry_create_error(JERRY_ERROR_TYPE,
181                                    reinterpret_cast<const jerry_char_t *>("param of router should be object."));
182         return false;
183     }
184     // check2:object's uri is undefined or is not string
185     jerry_value_t uriValue = jerryx_get_property_str(object_, ROUTER_PAGE_URI);
186     if (!jerry_value_is_string(uriValue)) {
187         jerry_release_value(uriValue);
188         HILOG_ERROR(HILOG_MODULE_ACE, "statemachine init failed as uri is invalid.");
189         jsRes =  AS_JERRY_VALUE(JSI::CreateErrorWithCode(JSI_ERR_CODE_PARAM_CHECK_FAILED,
190                                                          "uri value type should be string."));
191         return false;
192     }
193     uri_ = MallocStringOf(uriValue);
194     jerry_release_value(uriValue);
195     if (uri_ == nullptr) {
196         HILOG_ERROR(HILOG_MODULE_ACE, "statemachine init failed as uri is invalid.");
197         return false;
198     }
199     // check3:object's uri is empty
200     if (strlen(uri_) == 0) {
201         ace_free(uri_);
202         uri_ = nullptr;
203         HILOG_ERROR(HILOG_MODULE_ACE, "statemachine init failed as uri is empty.");
204         jsRes =
205             jerry_create_error(JERRY_ERROR_URI, reinterpret_cast<const jerry_char_t *>("uri value can't be empty."));
206         return false;
207     }
208 #ifdef _MINI_MULTI_TASKS_
209     // save current uri
210     appContext_->SetCurrentUri(uri_);
211 #endif
212 
213     int result = GenerateJsPagePath(uri_);
214     // check4:generate js file's path failed
215     if (result != SUCCESS) {
216         ace_free(uri_);
217         uri_ = nullptr;
218         HILOG_ERROR(HILOG_MODULE_ACE, "statemachine init failed as generating js file's path failed.");
219         jsRes = jerry_create_error(JERRY_ERROR_URI, reinterpret_cast<const jerry_char_t *>("uri value path error."));
220         return false;
221     }
222     // check5:object's uri is not existed, need to move
223     appContext_->SetCurrentJsPath(jsPagePath_);
224     if (!CheckJSSourceFile()) {
225         ace_free(uri_);
226         uri_ = nullptr;
227         HILOG_ERROR(HILOG_MODULE_ACE, "statemachine init failed as js file isn't existed.");
228         jsRes = AS_JERRY_VALUE(JSI::CreateErrorWithCode(ERR_CODE_URL_NOTEXIST, "route target doesn't existed."));
229         return false;
230     }
231     return true;
232 }
233 
CheckJSSourceFile() const234 bool StateMachine::CheckJSSourceFile() const
235 {
236     char *fullPath = RelocateJSSourceFilePath(appRootPath_, jsPagePath_);
237     if (fullPath == nullptr) {
238         return false;
239     }
240 
241     bool result = false;
242     do {
243         size_t pathLength = strlen(fullPath);
244         const uint8_t fileSuffixLength = 3;
245         if ((pathLength == 0) || (pathLength > PATH_LENGTH_MAX) || (pathLength < fileSuffixLength)) {
246             break;
247         }
248 
249         result = (GetFileSize(fullPath) > 0);
250         if (result) {
251             // try first one mode successfully
252             break;
253         }
254 
255         const char * const anotherSuffix = (JsAppEnvironment::GetInstance()->IsSnapshotMode()) ? ".js" : ".bc";
256         // change file suffix to another mode file
257         if (strcpy_s((fullPath + (pathLength - fileSuffixLength)), (fileSuffixLength + 1), anotherSuffix) != EOK) {
258             break;
259         }
260         result = (GetFileSize(fullPath) > 0);
261     } while (0);
262 
263     ace_free(fullPath);
264     fullPath = nullptr;
265 
266     return result;
267 }
268 
BindParameters()269 void StateMachine::BindParameters()
270 {
271     if (uri_ == nullptr) {
272         return;
273     }
274     jerry_value_t params = jerryx_get_property_str(object_, ROUTER_PAGE_PARAMS);
275     RegisterUriAndParamsToPage(uri_, params);
276     jerry_release_value(params);
277     ace_free(uri_);
278     uri_ = nullptr;
279 }
280 
ChangeState(int newState)281 void StateMachine::ChangeState(int newState)
282 {
283     if ((newState <= UNDEFINED_STATE) || (newState >= END_STATE)) {
284         HILOG_ERROR(HILOG_MODULE_ACE, "error input state:%{public}d", newState);
285         return;
286     }
287     // jump to new State
288     State *state = stateMap_[newState];
289     if (state != nullptr) {
290         state->Handle(*this);
291     }
292 }
293 
EvalPage()294 void StateMachine::EvalPage()
295 {
296     // load user's js code and eval it, rootPath/jsPagePath_.
297     // for example, router.replace("/"), should load rootPath/index/index.js
298     // router.replace("pages/detail/detail"), should load rootPath/pages/detail/detail.js
299     char *pageFilePath = RelocateJSSourceFilePath(appRootPath_, jsPagePath_);
300     if (pageFilePath == nullptr) {
301         HILOG_ERROR(HILOG_MODULE_ACE, "relocat page JS file failed");
302         return;
303     }
304 
305     jerry_value_t evalResult = appContext_->Eval(pageFilePath, strlen(pageFilePath), false);
306     if (IS_UNDEFINED(evalResult)) {
307         HILOG_ERROR(HILOG_MODULE_ACE, "Eval JS file failed");
308         ace_free(pageFilePath);
309         pageFilePath = nullptr;
310         return;
311     }
312     viewModel_ = evalResult;
313 
314     jerry_value_t params = jerryx_get_property_str(object_, ROUTER_PAGE_PARAMS);
315     jerry_value_t keys = jerry_get_object_keys(params);
316     uint16_t size = jerry_get_array_length(keys);
317     for (uint16_t idx = 0; idx < size; ++idx) {
318         jerry_value_t key = jerry_get_property_by_index(keys, idx);
319         jerry_value_t value = jerry_get_property(params, key);
320         jerry_release_value(jerry_set_property(viewModel_, key, value));
321         ReleaseJerryValue(value, key, VA_ARG_END_FLAG);
322     }
323     ReleaseJerryValue(keys, params, VA_ARG_END_FLAG);
324 
325     ace_free(pageFilePath);
326     pageFilePath = nullptr;
327 }
328 
ForceGC(void * data)329 static void ForceGC(void *data)
330 {
331     static_cast<void>(data);
332     jerry_gc(jerry_gc_mode_t::JERRY_GC_PRESSURE_HIGH);
333 #if IS_ENABLED(JS_PROFILER)
334     if (JSProfiler::GetInstance()->IsEnabled()) {
335         // dump the JS heap status
336         JSHeapStatus heapStatus;
337         if (JSI::GetJSHeapStatus(heapStatus)) {
338             HILOG_DEBUG(HILOG_MODULE_ACE, "JS Heap allocBytes[%{public}d], peakAllocBytes[%{public}d]",
339                         heapStatus.allocBytes, heapStatus.peakAllocBytes);
340         }
341     }
342 #endif
343 }
344 
RenderPage()345 void StateMachine::RenderPage()
346 {
347     START_TRACING(RENDER);
348     // if not in init state, reset all watchers of previous page at first
349     LazyLoadManager *lazy = const_cast<LazyLoadManager *>(appContext_->GetLazyLoadManager());
350     if (lazy != nullptr) {
351         lazy->ResetWatchers();
352     }
353     // Note: do not release the returned value by Render function, it will be released by component
354     jerry_value_t element = appContext_->Render(viewModel_);
355 
356     rootComponent_ = ComponentUtils::GetComponentFromBindingObject(element);
357     // append scroll layer to the outermost view
358     scrollLayer_ = new ScrollLayer();
359     if (scrollLayer_ != nullptr) {
360         scrollLayer_->AppendScrollLayer(rootComponent_);
361     }
362     Component::HandlerAnimations();
363     // trigger an async full GC after completing the heavy work, which will
364     // be executed after the whole page showing process
365     JsAsyncWork::DispatchAsyncWork(ForceGC, nullptr);
366     STOP_TRACING();
367 }
368 
ShowPage() const369 void StateMachine::ShowPage() const
370 {
371     if (isEntireHidden_) {
372         HILOG_WARN(HILOG_MODULE_ACE, "showpage: the whole application already in background, do not operate rootview");
373         return;
374     }
375     START_TRACING(ADD_TO_ROOT_VIEW);
376     if (scrollLayer_ != nullptr) {
377         scrollLayer_->Show();
378     }
379     STOP_TRACING();
380     SYS_MEMORY_TRACING();
381     JERRY_MEMORY_TRACING();
382 }
383 
HidePage() const384 void StateMachine::HidePage() const
385 {
386     if (isEntireHidden_) {
387         HILOG_WARN(HILOG_MODULE_ACE, "hidepage: the whole application already in background, do not operate rootview");
388         return;
389     }
390     if (scrollLayer_ != nullptr) {
391         scrollLayer_->Hide();
392     }
393     // trigger an async full GC after hiding the page
394     JsAsyncWork::DispatchAsyncWork(ForceGC, nullptr);
395 }
396 
InvokePageLifeCycleCallback(const char * const name) const397 void StateMachine::InvokePageLifeCycleCallback(const char * const name) const
398 {
399     if (FatalHandler::GetInstance().IsJSRuntimeFatal()) {
400         // can not continue to involve any JS object creating on engine in case runtime fatal
401         return;
402     }
403     if ((name == nullptr) || (strlen(name) == 0)) {
404         HILOG_ERROR(HILOG_MODULE_ACE, "input parameter is invalid when invoking page life cycle callback.");
405         return;
406     }
407 
408     jerry_value_t function = jerryx_get_property_str(viewModel_, name);
409     if (IS_UNDEFINED(function)) {
410         // user does not set onInit method
411         return;
412     }
413     CallJSFunctionAutoRelease(function, viewModel_, nullptr, 0);
414     jerry_release_value(function);
415 }
416 
ReleaseRootObject() const417 void StateMachine::ReleaseRootObject() const
418 {
419     if (FatalHandler::GetInstance().IsJSRuntimeFatal()) {
420         // can not continue to involve any JS object creating on engine in case runtime fatal
421         return;
422     }
423     // delete params properties from global object
424     jerry_value_t globalObject = jerry_get_global_object();
425     if (hasParams_) {
426         jerry_value_t pagePropName = jerry_create_string(reinterpret_cast<const jerry_char_t *>(ROUTER_PAGE));
427         bool success = jerry_delete_property(globalObject, pagePropName);
428         if (!success) {
429             HILOG_ERROR(HILOG_MODULE_ACE, "delete $page property failed from global object.");
430         }
431         jerry_release_value(pagePropName);
432     }
433 
434     // delete "$root" attribute from global object
435     jerry_value_t appPropName = jerry_create_string(reinterpret_cast<const jerry_char_t *>(ATTR_ROOT));
436     bool success = jerry_delete_property(globalObject, appPropName);
437     if (!success) {
438         HILOG_ERROR(HILOG_MODULE_ACE, "delete $root property failed from global object.");
439     }
440     ReleaseJerryValue(globalObject, appPropName, VA_ARG_END_FLAG);
441 }
442 
ReleaseHistoryPageResource()443 void StateMachine::ReleaseHistoryPageResource()
444 {
445     // remove all native views and release components styles.
446     if (appContext_ != nullptr) {
447         appContext_->ReleaseStyles();
448         appContext_->ReleaseLazyLoadManager();
449     }
450     // release scroll layer object.
451     if (scrollLayer_ != nullptr) {
452         if (!isEntireHidden_) {
453             // if the whole application is in background, avoid operating the rootview
454             scrollLayer_->DetachFromRootView();
455         }
456         delete (scrollLayer_);
457         scrollLayer_ = nullptr;
458     }
459     // if some fatal error happens and is hanled by FatalHandler, the resource is already
460     // recycled by it, do not repeat the recycling
461     if (!FatalHandler::GetInstance().IsFatalErrorHandleDone()) {
462         // release all native views and their binding js objects
463         ComponentUtils::ReleaseComponents(rootComponent_);
464         rootComponent_ = nullptr;
465     }
466 
467     ReleaseRootObject();
468 
469     // release current page's viewModel js object
470     jerry_release_value(viewModel_);
471 
472     // release animations
473     Component::ReleaseAnimations();
474 
475     // clean up native module objects required
476     ModuleManager::GetInstance()->CleanUpModule();
477     // check components leak
478     uint16_t remainComponentCount = FatalHandler::GetInstance().GetComponentCount();
479     if (remainComponentCount != 0) {
480         HILOG_ERROR(HILOG_MODULE_ACE, "[%{public}d] components leaked!", remainComponentCount);
481     }
482 }
483 
DeleteViewModelProperties() const484 void StateMachine::DeleteViewModelProperties() const
485 {
486     if (IS_UNDEFINED(viewModel_)) {
487         return;
488     }
489     jerry_value_t keys = jerry_get_object_keys(viewModel_);
490     uint32_t size = jerry_get_array_length(keys);
491     for (uint32_t index = 0; index < size; index++) {
492         jerry_value_t key = jerry_get_property_by_index(keys, index);
493         jerry_delete_property(viewModel_, key);
494         jerry_release_value(key);
495     }
496     jerry_release_value(keys);
497 }
498 
SetHiddenFlag(bool flag)499 void StateMachine::SetHiddenFlag(bool flag)
500 {
501     isEntireHidden_ = flag;
502 }
503 
504 #ifdef TDD_ASSERTIONS
SetViewModel(jerry_value_t viewModel)505 void StateMachine::SetViewModel(jerry_value_t viewModel)
506 {
507     DeleteViewModelProperties();
508     jerry_release_value(viewModel_);
509     viewModel_ = viewModel;
510     // should add all router param to new view model again?
511 }
512 #endif // TDD_ASSERTIONS
513 } // namespace ACELite
514 } // namespace OHOS
515