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