• 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 
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