• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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