1 /*
2  * Copyright (c) 2022 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 #ifndef FRAMEWORKS_BRIDGE_DECLARATIVE_FRONTEND_JS_VIEW_JS_LAZY_FOREACH_BUILDER_H
17 #define FRAMEWORKS_BRIDGE_DECLARATIVE_FRONTEND_JS_VIEW_JS_LAZY_FOREACH_BUILDER_H
18 
19 #include <functional>
20 #include <set>
21 #include <string>
22 
23 #include "base/memory/ace_type.h"
24 #include "bridge/declarative_frontend/jsview/js_lazy_foreach_actuator.h"
25 #include "bridge/declarative_frontend/jsview/js_view.h"
26 #include "core/components_ng/base/view_stack_processor.h"
27 #include "core/components_ng/syntax/lazy_for_each_builder.h"
28 
29 namespace OHOS::Ace::Framework {
30 
31 class JSLazyForEachBuilder : public NG::LazyForEachBuilder, public JSLazyForEachActuator {
32     DECLARE_ACE_TYPE(JSLazyForEachBuilder, NG::LazyForEachBuilder, JSLazyForEachActuator);
33 
34 public:
35     JSLazyForEachBuilder() = default;
~JSLazyForEachBuilder()36     ~JSLazyForEachBuilder()
37     {
38         changedLazyForEachNodes_.clear();
39         dependElementIds_.clear();
40     };
41 
OnGetTotalCount()42     int32_t OnGetTotalCount() override
43     {
44         return GetTotalIndexCount();
45     }
46 
OnExpandChildrenOnInitialInNG()47     void OnExpandChildrenOnInitialInNG() override
48     {
49         auto totalIndex = GetTotalIndexCount();
50         auto* stack = NG::ViewStackProcessor::GetInstance();
51         JSRef<JSVal> params[paramType::MIN_PARAMS_SIZE];
52         for (auto index = 0; index < totalIndex; index++) {
53             params[paramType::Data] = CallJSFunction(getDataFunc_, dataSourceObj_, index);
54             params[paramType::Index] = JSRef<JSVal>::Make(ToJSValue(index));
55             params[paramType::Initialize] = JSRef<JSVal>::Make(ToJSValue(true));
56             std::string key = keyGenFunc_(params[paramType::Data], index);
57             stack->PushKey(key);
58             itemGenFunc_->Call(JSRef<JSObject>(), paramType::MIN_PARAMS_SIZE, params);
59             stack->PopKey();
60         }
61     }
62 
NotifyDataAdded(size_t index)63     void NotifyDataAdded(size_t index) override
64     {
65         if (updateChangedNodeFlag_) {
66             decltype(changedLazyForEachNodes_) temp(std::move(changedLazyForEachNodes_));
67             for (auto& [oldindex, child] : temp) {
68                 changedLazyForEachNodes_.try_emplace(
69                     index > static_cast<size_t>(oldindex) ? oldindex : oldindex + 1, std::move(child));
70             }
71         }
72     }
73 
NotifyDataChanged(size_t index,const RefPtr<NG::UINode> & lazyForEachNode,bool isRebuild)74     void NotifyDataChanged(size_t index, const RefPtr<NG::UINode>& lazyForEachNode, bool isRebuild) override
75     {
76         if (updateChangedNodeFlag_ && !isRebuild) {
77             changedLazyForEachNodes_[index] = lazyForEachNode;
78         }
79     }
80 
NotifyDataDeleted(const RefPtr<NG::UINode> & lazyForEachNode,size_t index,bool removeIds)81     void NotifyDataDeleted(const RefPtr<NG::UINode>& lazyForEachNode, size_t index, bool removeIds) override
82     {
83         if (updateChangedNodeFlag_) {
84             decltype(changedLazyForEachNodes_) temp(std::move(changedLazyForEachNodes_));
85             for (auto& [oldindex, child] : temp) {
86                 if (static_cast<size_t>(oldindex) != index && lazyForEachNode != child) {
87                     changedLazyForEachNodes_.try_emplace(
88                         index > static_cast<size_t>(oldindex) ? oldindex : oldindex - 1, std::move(child));
89                 }
90             }
91             if (removeIds) {
92                 dependElementIds_.erase(lazyForEachNode);
93             }
94         }
95     }
96 
KeepRemovedItemInCache(NG::LazyForEachChild node,std::unordered_map<std::string,NG::LazyForEachCacheChild> & cachedItems)97     void KeepRemovedItemInCache(NG::LazyForEachChild node,
98         std::unordered_map<std::string, NG::LazyForEachCacheChild>& cachedItems) override
99     {
100         if (updateChangedNodeFlag_) {
101             cachedItems.try_emplace(node.first, NG::LazyForEachCacheChild(-1, node.second));
102         }
103     }
104 
UpdateDependElmtIds(RefPtr<NG::UINode> & node,JSRef<JSVal> & jsElmtIds,std::string key)105     std::string UpdateDependElmtIds(RefPtr<NG::UINode>& node, JSRef<JSVal>& jsElmtIds, std::string key)
106     {
107         std::string lastKey;
108         if (jsElmtIds->IsArray()) {
109             JSRef<JSArray> jsElmtIdArray = JSRef<JSArray>::Cast(jsElmtIds);
110             for (size_t i = 0; i < jsElmtIdArray->Length(); i++) {
111                 JSRef<JSVal> jsElmtId = jsElmtIdArray->GetValueAt(i);
112                 if (jsElmtId->IsNumber()) {
113                     dependElementIds_[node].first.insert(jsElmtId->ToNumber<uint32_t>());
114                 }
115             }
116             lastKey = dependElementIds_[node].second;
117             dependElementIds_[node].second = key;
118         }
119         return lastKey;
120     }
121 
SetToJSVal(std::set<uint32_t> elmtIds)122     JSRef<JSVal> SetToJSVal(std::set<uint32_t> elmtIds)
123     {
124         JSRef<JSArray> jsElmtIdArray = JSRef<JSArray>::New();
125         int32_t count = 0;
126         for (auto& elmtId : elmtIds) {
127             jsElmtIdArray->SetValueAt(count, JSRef<JSVal>::Make(ToJSValue(elmtId)));
128             count++;
129         }
130         return JSRef<JSVal>::Cast(jsElmtIdArray);
131     }
132 
OnGetChildByIndex(int32_t index,std::unordered_map<std::string,NG::LazyForEachCacheChild> & cachedItems)133     std::pair<std::string, RefPtr<NG::UINode>> OnGetChildByIndex(
134         int32_t index, std::unordered_map<std::string, NG::LazyForEachCacheChild>& cachedItems) override
135     {
136         std::pair<std::string, RefPtr<NG::UINode>> info;
137         JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(executionContext_, info);
138         if (getDataFunc_.IsEmpty()) {
139             return info;
140         }
141 
142         JSRef<JSVal> params[paramType::MAX_PARAMS_SIZE];
143         params[paramType::Data] = CallJSFunction(getDataFunc_, dataSourceObj_, index);
144         params[paramType::Index] = JSRef<JSVal>::Make(ToJSValue(index));
145         std::string key = keyGenFunc_(params[paramType::Data], index);
146         auto cachedIter = cachedItems.find(key);
147         if (cachedIter != cachedItems.end()) {
148             info.first = key;
149             info.second = cachedIter->second.second;
150             cachedItems.erase(cachedIter);
151             return info;
152         }
153 
154         auto nodeIter = changedLazyForEachNodes_.find(index);
155         if (updateChangedNodeFlag_ && nodeIter != changedLazyForEachNodes_.end()) {
156             RefPtr<NG::UINode> lazyForEachNode = nodeIter->second;
157             info.first = key;
158             info.second = lazyForEachNode;
159             params[paramType::Initialize] = JSRef<JSVal>::Make(ToJSValue(false));
160             params[paramType::ElmtIds] = SetToJSVal(dependElementIds_[lazyForEachNode].first);
161 
162             auto jsElmtIds = itemGenFunc_->Call(JSRef<JSObject>(), paramType::MAX_PARAMS_SIZE, params);
163             std::string lastKey = UpdateDependElmtIds(info.second, jsElmtIds, key);
164             changedLazyForEachNodes_.erase(nodeIter);
165             cachedItems.erase(lastKey);
166             return info;
167         }
168 
169         NG::ScopedViewStackProcessor scopedViewStackProcessor;
170         auto* viewStack = NG::ViewStackProcessor::GetInstance();
171         if (parentView_) {
172             parentView_->MarkLazyForEachProcess(key);
173         }
174         viewStack->PushKey(key);
175         params[paramType::Initialize] = JSRef<JSVal>::Make(ToJSValue(true));
176         JSRef<JSVal> jsElmtIds = itemGenFunc_->Call(JSRef<JSObject>(), paramType::MIN_PARAMS_SIZE, params);
177         viewStack->PopKey();
178         if (parentView_) {
179             parentView_->ResetLazyForEachProcess();
180         }
181         info.first = key;
182         info.second = viewStack->Finish();
183         if (updateChangedNodeFlag_) {
184             UpdateDependElmtIds(info.second, jsElmtIds, key);
185         }
186         return info;
187     }
188 
OnGetChildByIndexNew(int32_t index,std::map<int32_t,NG::LazyForEachChild> & cachedItems,std::unordered_map<std::string,NG::LazyForEachCacheChild> & expiringItems)189     std::pair<std::string, RefPtr<NG::UINode>> OnGetChildByIndexNew(int32_t index,
190         std::map<int32_t, NG::LazyForEachChild>& cachedItems,
191         std::unordered_map<std::string, NG::LazyForEachCacheChild>& expiringItems) override
192     {
193         std::pair<std::string, RefPtr<NG::UINode>> info;
194         JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(executionContext_, info);
195         if (getDataFunc_.IsEmpty()) {
196             return info;
197         }
198 
199         JSRef<JSVal> params[paramType::MAX_PARAMS_SIZE];
200         params[paramType::Data] = CallJSFunction(getDataFunc_, dataSourceObj_, index);
201         params[paramType::Index] = JSRef<JSVal>::Make(ToJSValue(index));
202         std::string key;
203         auto cachedIter = cachedItems.find(index);
204         if (cachedIter != cachedItems.end() && !cachedIter->second.first.empty()) {
205             key = cachedIter->second.first;
206         } else {
207             key = keyGenFunc_(params[paramType::Data], index);
208         }
209 
210         auto expiringIter = expiringItems.find(key);
211         if (expiringIter != expiringItems.end()) {
212             info.first = key;
213             info.second = expiringIter->second.second;
214             expiringItems.erase(expiringIter);
215             // if info.second is null, the following ui node creation process is needed to fill info.second
216             if (info.second != nullptr) {
217                 return info;
218             }
219         }
220 
221         NG::ScopedViewStackProcessor scopedViewStackProcessor;
222         auto* viewStack = NG::ViewStackProcessor::GetInstance();
223         if (parentView_) {
224             parentView_->MarkLazyForEachProcess(key);
225         }
226         viewStack->PushKey(key);
227         params[paramType::Initialize] = JSRef<JSVal>::Make(ToJSValue(true));
228         itemGenFunc_->Call(JSRef<JSObject>(), paramType::MIN_PARAMS_SIZE, params);
229         viewStack->PopKey();
230         if (parentView_) {
231             parentView_->ResetLazyForEachProcess();
232         }
233         info.first = key;
234         info.second = viewStack->Finish();
235         return info;
236     }
237 
ReleaseChildGroupById(const std::string & id)238     void ReleaseChildGroupById(const std::string& id) override
239     {
240         JSLazyForEachActuator::ReleaseChildGroupByComposedId(id);
241     }
242 
RegisterDataChangeListener(const RefPtr<V2::DataChangeListener> & listener)243     void RegisterDataChangeListener(const RefPtr<V2::DataChangeListener>& listener) override
244     {
245         JSLazyForEachActuator::RegisterListener(listener);
246     }
247 
UnregisterDataChangeListener(V2::DataChangeListener * listener)248     void UnregisterDataChangeListener(V2::DataChangeListener* listener) override
249     {
250         JSLazyForEachActuator::UnregisterListener(listener);
251     }
252 
253     ACE_DISALLOW_COPY_AND_MOVE(JSLazyForEachBuilder);
254 
255 private:
256     std::map<int32_t, RefPtr<NG::UINode>> changedLazyForEachNodes_;
257     std::map<RefPtr<NG::UINode>, std::pair<std::set<uint32_t>, std::string>> dependElementIds_;
258     enum paramType {Data = 0, Index, Initialize, ElmtIds, MIN_PARAMS_SIZE = ElmtIds, MAX_PARAMS_SIZE};
259 };
260 
261 } // namespace OHOS::Ace::Framework
262 
263 #endif // FRAMEWORKS_BRIDGE_DECLARATIVE_FRONTEND_JS_VIEW_JS_LAZY_FOREACH_BUILDER_H
264