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 int result = GenerateJsPagePath(uri_);
209 // check4:generate js file's path failed
210 if (result != SUCCESS) {
211 ace_free(uri_);
212 uri_ = nullptr;
213 HILOG_ERROR(HILOG_MODULE_ACE, "statemachine init failed as generating js file's path failed.");
214 jsRes = jerry_create_error(JERRY_ERROR_URI, reinterpret_cast<const jerry_char_t *>("uri value path error."));
215 return false;
216 }
217 // check5:object's uri is not existed, need to move
218 appContext_->SetCurrentJsPath(jsPagePath_);
219 if (!CheckJSSourceFile()) {
220 ace_free(uri_);
221 uri_ = nullptr;
222 HILOG_ERROR(HILOG_MODULE_ACE, "statemachine init failed as js file isn't existed.");
223 jsRes = AS_JERRY_VALUE(JSI::CreateErrorWithCode(ERR_CODE_URL_NOTEXIST, "route target doesn't existed."));
224 return false;
225 }
226 return true;
227 }
228
CheckJSSourceFile() const229 bool StateMachine::CheckJSSourceFile() const
230 {
231 char *fullPath = RelocateJSSourceFilePath(appRootPath_, jsPagePath_);
232 if (fullPath == nullptr) {
233 return false;
234 }
235
236 bool result = false;
237 do {
238 size_t pathLength = strlen(fullPath);
239 const uint8_t fileSuffixLength = 3;
240 if ((pathLength == 0) || (pathLength > PATH_LENGTH_MAX) || (pathLength < fileSuffixLength)) {
241 break;
242 }
243
244 result = (GetFileSize(fullPath) > 0);
245 if (result) {
246 // try first one mode successfully
247 break;
248 }
249
250 const char * const anotherSuffix = (JsAppEnvironment::GetInstance()->IsSnapshotMode()) ? ".js" : ".bc";
251 // change file suffix to another mode file
252 if (strcpy_s((fullPath + (pathLength - fileSuffixLength)), (fileSuffixLength + 1), anotherSuffix) != EOK) {
253 break;
254 }
255 result = (GetFileSize(fullPath) > 0);
256 } while (0);
257
258 ace_free(fullPath);
259 fullPath = nullptr;
260
261 return result;
262 }
263
BindParameters()264 void StateMachine::BindParameters()
265 {
266 if (uri_ == nullptr) {
267 return;
268 }
269 jerry_value_t params = jerryx_get_property_str(object_, ROUTER_PAGE_PARAMS);
270 RegisterUriAndParamsToPage(uri_, params);
271 jerry_release_value(params);
272 ace_free(uri_);
273 uri_ = nullptr;
274 }
275
ChangeState(int newState)276 void StateMachine::ChangeState(int newState)
277 {
278 if ((newState <= UNDEFINED_STATE) || (newState >= END_STATE)) {
279 HILOG_ERROR(HILOG_MODULE_ACE, "error input state:%{public}d", newState);
280 return;
281 }
282 // jump to new State
283 State *state = stateMap_[newState];
284 if (state != nullptr) {
285 state->Handle(*this);
286 }
287 }
288
EvalPage()289 void StateMachine::EvalPage()
290 {
291 // load user's js code and eval it, rootPath/jsPagePath_.
292 // for example, router.replace("/"), should load rootPath/index/index.js
293 // router.replace("pages/detail/detail"), should load rootPath/pages/detail/detail.js
294 char *pageFilePath = RelocateJSSourceFilePath(appRootPath_, jsPagePath_);
295 if (pageFilePath == nullptr) {
296 HILOG_ERROR(HILOG_MODULE_ACE, "relocat page JS file failed");
297 return;
298 }
299
300 jerry_value_t evalResult = appContext_->Eval(pageFilePath, strlen(pageFilePath), false);
301 if (IS_UNDEFINED(evalResult)) {
302 HILOG_ERROR(HILOG_MODULE_ACE, "Eval JS file failed");
303 ace_free(pageFilePath);
304 pageFilePath = nullptr;
305 return;
306 }
307 viewModel_ = evalResult;
308
309 jerry_value_t params = jerryx_get_property_str(object_, ROUTER_PAGE_PARAMS);
310 jerry_value_t keys = jerry_get_object_keys(params);
311 uint16_t size = jerry_get_array_length(keys);
312 for (uint16_t idx = 0; idx < size; ++idx) {
313 jerry_value_t key = jerry_get_property_by_index(keys, idx);
314 jerry_value_t value = jerry_get_property(params, key);
315 jerry_release_value(jerry_set_property(viewModel_, key, value));
316 ReleaseJerryValue(value, key, VA_ARG_END_FLAG);
317 }
318 ReleaseJerryValue(keys, params, VA_ARG_END_FLAG);
319
320 ace_free(pageFilePath);
321 pageFilePath = nullptr;
322 }
323
ForceGC(void * data)324 static void ForceGC(void *data)
325 {
326 static_cast<void>(data);
327 jerry_gc(jerry_gc_mode_t::JERRY_GC_PRESSURE_HIGH);
328 #if IS_ENABLED(JS_PROFILER)
329 if (JSProfiler::GetInstance()->IsEnabled()) {
330 // dump the JS heap status
331 JSHeapStatus heapStatus;
332 if (JSI::GetJSHeapStatus(heapStatus)) {
333 HILOG_DEBUG(HILOG_MODULE_ACE, "JS Heap allocBytes[%{public}d], peakAllocBytes[%{public}d]",
334 heapStatus.allocBytes, heapStatus.peakAllocBytes);
335 }
336 }
337 #endif
338 }
339
RenderPage()340 void StateMachine::RenderPage()
341 {
342 START_TRACING(RENDER);
343 // if not in init state, reset all watchers of previous page at first
344 LazyLoadManager *lazy = const_cast<LazyLoadManager *>(appContext_->GetLazyLoadManager());
345 if (lazy != nullptr) {
346 lazy->ResetWatchers();
347 }
348 // Note: do not release the returned value by Render function, it will be released by component
349 jerry_value_t element = appContext_->Render(viewModel_);
350
351 rootComponent_ = ComponentUtils::GetComponentFromBindingObject(element);
352 // append scroll layer to the outermost view
353 scrollLayer_ = new ScrollLayer();
354 if (scrollLayer_ != nullptr) {
355 scrollLayer_->AppendScrollLayer(rootComponent_);
356 }
357 Component::HandlerAnimations();
358 // trigger an async full GC after completing the heavy work, which will
359 // be executed after the whole page showing process
360 JsAsyncWork::DispatchAsyncWork(ForceGC, nullptr);
361 STOP_TRACING();
362 }
363
ShowPage() const364 void StateMachine::ShowPage() const
365 {
366 if (isEntireHidden_) {
367 HILOG_WARN(HILOG_MODULE_ACE, "showpage: the whole application already in background, do not operate rootview");
368 return;
369 }
370 START_TRACING(ADD_TO_ROOT_VIEW);
371 if (scrollLayer_ != nullptr) {
372 scrollLayer_->Show();
373 }
374 STOP_TRACING();
375 SYS_MEMORY_TRACING();
376 JERRY_MEMORY_TRACING();
377 }
378
HidePage() const379 void StateMachine::HidePage() const
380 {
381 if (isEntireHidden_) {
382 HILOG_WARN(HILOG_MODULE_ACE, "hidepage: the whole application already in background, do not operate rootview");
383 return;
384 }
385 if (scrollLayer_ != nullptr) {
386 scrollLayer_->Hide();
387 }
388 // trigger an async full GC after hiding the page
389 JsAsyncWork::DispatchAsyncWork(ForceGC, nullptr);
390 }
391
InvokePageLifeCycleCallback(const char * const name) const392 void StateMachine::InvokePageLifeCycleCallback(const char * const name) const
393 {
394 if (FatalHandler::GetInstance().IsJSRuntimeFatal()) {
395 // can not continue to involve any JS object creating on engine in case runtime fatal
396 return;
397 }
398 if ((name == nullptr) || (strlen(name) == 0)) {
399 HILOG_ERROR(HILOG_MODULE_ACE, "input parameter is invalid when invoking page life cycle callback.");
400 return;
401 }
402
403 jerry_value_t function = jerryx_get_property_str(viewModel_, name);
404 if (IS_UNDEFINED(function)) {
405 // user does not set onInit method
406 return;
407 }
408 CallJSFunctionAutoRelease(function, viewModel_, nullptr, 0);
409 jerry_release_value(function);
410 }
411
ReleaseRootObject() const412 void StateMachine::ReleaseRootObject() const
413 {
414 if (FatalHandler::GetInstance().IsJSRuntimeFatal()) {
415 // can not continue to involve any JS object creating on engine in case runtime fatal
416 return;
417 }
418 // delete params properties from global object
419 jerry_value_t globalObject = jerry_get_global_object();
420 if (hasParams_) {
421 jerry_value_t pagePropName = jerry_create_string(reinterpret_cast<const jerry_char_t *>(ROUTER_PAGE));
422 bool success = jerry_delete_property(globalObject, pagePropName);
423 if (!success) {
424 HILOG_ERROR(HILOG_MODULE_ACE, "delete $page property failed from global object.");
425 }
426 jerry_release_value(pagePropName);
427 }
428
429 // delete "$root" attribute from global object
430 jerry_value_t appPropName = jerry_create_string(reinterpret_cast<const jerry_char_t *>(ATTR_ROOT));
431 bool success = jerry_delete_property(globalObject, appPropName);
432 if (!success) {
433 HILOG_ERROR(HILOG_MODULE_ACE, "delete $root property failed from global object.");
434 }
435 ReleaseJerryValue(globalObject, appPropName, VA_ARG_END_FLAG);
436 }
437
ReleaseHistoryPageResource()438 void StateMachine::ReleaseHistoryPageResource()
439 {
440 // remove all native views and release components styles.
441 if (appContext_ != nullptr) {
442 appContext_->ReleaseStyles();
443 appContext_->ReleaseLazyLoadManager();
444 }
445 // release scroll layer object.
446 if (scrollLayer_ != nullptr) {
447 if (!isEntireHidden_) {
448 // if the whole application is in background, avoid operating the rootview
449 scrollLayer_->DetachFromRootView();
450 }
451 delete (scrollLayer_);
452 scrollLayer_ = nullptr;
453 }
454 // if some fatal error happens and is hanled by FatalHandler, the resource is already
455 // recycled by it, do not repeat the recycling
456 if (!FatalHandler::GetInstance().IsFatalErrorHandleDone()) {
457 // release all native views and their binding js objects
458 ComponentUtils::ReleaseComponents(rootComponent_);
459 rootComponent_ = nullptr;
460 }
461
462 ReleaseRootObject();
463
464 // release current page's viewModel js object
465 jerry_release_value(viewModel_);
466
467 // release animations
468 Component::ReleaseAnimations();
469
470 // clean up native module objects required
471 ModuleManager::GetInstance()->CleanUpModule();
472 // check components leak
473 uint16_t remainComponentCount = FatalHandler::GetInstance().GetComponentCount();
474 if (remainComponentCount != 0) {
475 HILOG_ERROR(HILOG_MODULE_ACE, "[%{public}d] components leaked!", remainComponentCount);
476 }
477 }
478
DeleteViewModelProperties() const479 void StateMachine::DeleteViewModelProperties() const
480 {
481 if (IS_UNDEFINED(viewModel_)) {
482 return;
483 }
484 jerry_value_t keys = jerry_get_object_keys(viewModel_);
485 uint32_t size = jerry_get_array_length(keys);
486 for (uint32_t index = 0; index < size; index++) {
487 jerry_value_t key = jerry_get_property_by_index(keys, index);
488 jerry_delete_property(viewModel_, key);
489 jerry_release_value(key);
490 }
491 jerry_release_value(keys);
492 }
493
SetHiddenFlag(bool flag)494 void StateMachine::SetHiddenFlag(bool flag)
495 {
496 isEntireHidden_ = flag;
497 }
498
499 #ifdef TDD_ASSERTIONS
SetViewModel(jerry_value_t viewModel)500 void StateMachine::SetViewModel(jerry_value_t viewModel)
501 {
502 DeleteViewModelProperties();
503 jerry_release_value(viewModel_);
504 viewModel_ = viewModel;
505 // should add all router param to new view model again?
506 }
507 #endif // TDD_ASSERTIONS
508 } // namespace ACELite
509 } // namespace OHOS
510