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