• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-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 #include "frameworks/bridge/js_frontend/engine/quickjs/component_api_bridge.h"
17 
18 #include "core/animation/curves.h"
19 #include "frameworks/bridge/common/dom/dom_list.h"
20 #include "frameworks/bridge/common/utils/utils.h"
21 
22 namespace OHOS::Ace::Framework {
23 
JsGetScrollOffset(JSContext * ctx,NodeId nodeId)24 JSValue ComponentApiBridge::JsGetScrollOffset(JSContext* ctx, NodeId nodeId)
25 {
26     auto instance = static_cast<QjsEngineInstance*>(JS_GetContextOpaque(ctx));
27     if (instance == nullptr) {
28         return JS_NULL;
29     }
30     auto page = instance->GetRunningPage();
31     if (!page) {
32         return JS_NULL;
33     }
34     Offset offset;
35     auto task = [nodeId, page, &offset]() {
36         auto domDoc = page->GetDomDocument();
37         if (!domDoc) {
38             return;
39         }
40 
41         auto domNode = domDoc->GetDOMNodeById(nodeId);
42         if (!domNode) {
43             return;
44         }
45         auto domList = AceType::DynamicCast<DOMList>(domNode);
46         if (domList) {
47             offset = domList->GetCurrentOffset();
48             return;
49         }
50 
51         auto scrollComponent = domNode->GetScrollComponent();
52         if (!scrollComponent) {
53             return;
54         }
55         auto controller = scrollComponent->GetScrollPositionController();
56         if (!controller) {
57             return;
58         }
59         offset = controller->GetCurrentOffset();
60     };
61     auto delegate = instance->GetDelegate();
62     if (!delegate) {
63         return JS_NULL;
64     }
65     delegate->PostSyncTaskToPage(task);
66     JSValue offsetContext = JS_NewObject(ctx);
67     JS_SetPropertyStr(ctx, offsetContext, "x", JS_NewFloat64(ctx, offset.GetX()));
68     JS_SetPropertyStr(ctx, offsetContext, "y", JS_NewFloat64(ctx, offset.GetY()));
69     return offsetContext;
70 }
71 
JsGetBoundingRect(JSContext * ctx,NodeId nodeId)72 JSValue ComponentApiBridge::JsGetBoundingRect(JSContext* ctx, NodeId nodeId)
73 {
74     auto instance = static_cast<QjsEngineInstance*>(JS_GetContextOpaque(ctx));
75     if (instance == nullptr) {
76         return JS_NULL;
77     }
78     auto delegate = instance->GetDelegate();
79     if (!delegate) {
80         return JS_NULL;
81     }
82     Rect boundingRect = delegate->GetBoundingRectData(nodeId);
83     JSValue rectContext = JS_NewObject(ctx);
84     JS_SetPropertyStr(ctx, rectContext, "width", JS_NewFloat64(ctx, boundingRect.Width()));
85     JS_SetPropertyStr(ctx, rectContext, "height", JS_NewFloat64(ctx, boundingRect.Height()));
86     JS_SetPropertyStr(ctx, rectContext, "top", JS_NewFloat64(ctx, boundingRect.Top()));
87     JS_SetPropertyStr(ctx, rectContext, "left", JS_NewFloat64(ctx, boundingRect.Left()));
88     return rectContext;
89 }
90 
JsGetInspector(JSContext * ctx,NodeId nodeId)91 JSValue ComponentApiBridge::JsGetInspector(JSContext* ctx, NodeId nodeId)
92 {
93     auto instance = static_cast<QjsEngineInstance*>(JS_GetContextOpaque(ctx));
94     if (instance == nullptr) {
95         return JS_NULL;
96     }
97     auto delegate = instance->GetDelegate();
98     if (!delegate) {
99         return JS_NULL;
100     }
101     auto attributes = delegate->GetInspector(nodeId);
102     JSValue result = JS_NewString(ctx, attributes.c_str());
103     return result;
104 }
105 
JsScrollTo(JSContext * ctx,const std::string & args,NodeId nodeId)106 void ComponentApiBridge::JsScrollTo(JSContext* ctx, const std::string& args, NodeId nodeId)
107 {
108     auto instance = static_cast<QjsEngineInstance*>(JS_GetContextOpaque(ctx));
109     if (instance == nullptr) {
110         LOGE("ComponentApiBridge::JsScrollTo instance is null");
111         return;
112     }
113     auto page = instance->GetRunningPage();
114     if (!page) {
115         LOGE("ComponentApiBridge::JsScrollTo page is null");
116         return;
117     }
118 
119     auto task = [nodeId, page, args]() {
120         auto domDoc = page->GetDomDocument();
121         if (!domDoc) {
122             LOGE("ComponentApiBridge::JsScrollTo dom document is null!");
123             return;
124         }
125 
126         auto domNode = domDoc->GetDOMNodeById(nodeId);
127         if (!domNode) {
128             LOGE("ComponentApiBridge::JsScrollTo node not exist!");
129             return;
130         }
131 
132         std::unique_ptr<JsonValue> argsValue = JsonUtil::ParseJsonString(args);
133         if (!argsValue || !argsValue->IsArray() || argsValue->GetArraySize() < 1) {
134             LOGE("parse args error");
135             return;
136         }
137         std::unique_ptr<JsonValue> scrollToPara = argsValue->GetArrayItem(0);
138         if (!scrollToPara->IsValid()) {
139             return;
140         }
141         int32_t index = scrollToPara->GetInt("index", 0);
142         auto domList = AceType::DynamicCast<DOMList>(domNode);
143         if (domList) {
144             // list has specialized scrollTo method.
145             domList->ScrollToMethod(index);
146             return;
147         }
148 
149         auto scrollComponent = domNode->GetScrollComponent();
150         if (!scrollComponent) {
151             return;
152         }
153         auto controller = scrollComponent->GetScrollPositionController();
154         if (!controller) {
155             return;
156         }
157 
158         std::string id = scrollToPara->GetString("id", "");
159         double position = scrollToPara->GetDouble("position", 0.0);
160         double duration = scrollToPara->GetDouble("duration", 300.0); // Default duration is 300ms.
161         std::string timingFunction = scrollToPara->GetString("timingFunction", "ease");
162         std::string successId = scrollToPara->GetString("success", "");
163         std::string failId = scrollToPara->GetString("fail", "");
164         std::string completeId = scrollToPara->GetString("complete", "");
165         auto context = domNode->GetPipelineContext();
166         auto callback = [context, successId, completeId]() {
167             auto refContext = context.Upgrade();
168             if (refContext) {
169                 refContext->SendCallbackMessageToFrontend(successId, std::string("\"success\",null"));
170                 refContext->SendCallbackMessageToFrontend(completeId, std::string("\"complete\",null"));
171             }
172         };
173 
174         bool result = false;
175         if (scrollToPara->Contains("position")) {
176             result = controller->AnimateTo(position, duration, CreateCurve(timingFunction), false, callback);
177         } else if (scrollToPara->Contains("id") && !id.empty()) {
178             result = controller->AnimateToTarget(id, duration, CreateCurve(timingFunction), false, callback);
179         } else {
180             LOGW("ComponentApiBridge::JsScrollTo param not valid.");
181         }
182         if (!result) {
183             auto refContext = context.Upgrade();
184             if (refContext) {
185                 refContext->SendCallbackMessageToFrontend(failId, std::string("\"fail\",null"));
186                 refContext->SendCallbackMessageToFrontend(completeId, std::string("\"complete\",null"));
187             }
188         }
189     };
190 
191     auto delegate = instance->GetDelegate();
192     if (!delegate) {
193         LOGE("ComponentApiBridge::JsScrollTo delegate is null");
194         return;
195     }
196     delegate->PostSyncTaskToPage(task);
197 }
198 
JsCreateObserver(JSContext * ctx,const std::string & args,NodeId nodeId)199 JSValue ComponentApiBridge::JsCreateObserver(JSContext* ctx, const std::string& args, NodeId nodeId)
200 {
201     LOGI("ComponentApiBridge::JsCreateObserver");
202     auto instance = static_cast<QjsEngineInstance*>(JS_GetContextOpaque(ctx));
203     if (instance == nullptr) {
204         LOGE("instance is null");
205         return JS_NULL;
206     }
207     auto observer = JS_NewObject(ctx);
208     std::unique_ptr<JsonValue> argsPtr = JsonUtil::ParseJsonString(args);
209     if (!argsPtr) {
210         LOGE("argsPtr is null");
211         return JS_NULL;
212     }
213     auto observerParam = argsPtr->GetArrayItem(0);
214     if (!observerParam) {
215         LOGE("observerParam is null");
216         return JS_NULL;
217     }
218     double ratio = 0.5;
219     if (observerParam->GetValue("ratio") != nullptr && observerParam->GetValue("ratio")->IsNumber()) {
220         ratio = observerParam->GetValue("ratio")->GetDouble();
221     }
222     JS_SetPropertyStr(ctx, observer, "__nodeId", JS_NewInt32(ctx, nodeId));
223     JS_SetPropertyStr(ctx, observer, "__ratio", JS_NewInt32(ctx, ratio));
224     JS_SetPropertyStr(ctx, observer, "observe", JS_NewCFunction(ctx, ComponentApiBridge::JsObserverOn, "observe", 1));
225     JS_SetPropertyStr(
226         ctx, observer, "unobserve", JS_NewCFunction(ctx, ComponentApiBridge::JsObserverOff, "unobserve", 0));
227     return observer;
228 }
229 
JsObserverOn(JSContext * ctx,JSValueConst value,int32_t argc,JSValueConst * argv)230 JSValue ComponentApiBridge::JsObserverOn(JSContext* ctx, JSValueConst value, int32_t argc, JSValueConst* argv)
231 {
232     LOGD("ComponentApiBridge::JsObserverOn");
233     if ((!argv) || argc != 1) {
234         return JS_NULL;
235     }
236     auto instance = static_cast<QjsEngineInstance*>(JS_GetContextOpaque(ctx));
237     if (!instance) {
238         LOGE("no instance");
239         return JS_NULL;
240     }
241     if (!JS_IsFunction(ctx, argv[0])) {
242         return JS_NULL;
243     }
244     NodeId id = 0;
245     JSValue nodeId = JS_GetPropertyStr(ctx, value, "__nodeId");
246     if (JS_IsInteger(nodeId) && (JS_ToInt32(ctx, &id, nodeId)) < 0) {
247         id = 0;
248     }
249     JS_FreeValue(ctx, nodeId);
250 
251     JSValue ratioVal = JS_GetPropertyStr(ctx, value, "__ratio");
252     double ratio = 0.0;
253     ScopedString scopedVal(ctx, ratioVal);
254     auto jsonVal = JsonUtil::ParseJsonData(scopedVal.get());
255     if (jsonVal && jsonVal->IsNumber()) {
256         ratio = jsonVal->GetDouble();
257     }
258 
259     auto callbackJsObject = AceType::MakeRefPtr<QJSVisibleListenerCallback>(ctx, argv[0], id);
260     auto jsCallback = [callbackObj = callbackJsObject](bool visible, double ratio) {
261         JSContext* context = callbackObj->GetContext();
262         QJSHandleScope handleScope(context);
263         auto jsFunc = callbackObj->GetJsObject();
264         JSValue globalObj = JS_GetGlobalObject(context);
265 
266         JSValueConst argv[] = {
267             JS_NewBool(context, visible),
268             JS_NewFloat64(context, ratio),
269         };
270         JSValue retVal = QJSUtils::Call(context, jsFunc, globalObj, countof(argv), argv);
271         if (JS_IsException(retVal)) {
272             LOGE("JS framework load js bundle failed!");
273             JS_FreeValue(context, globalObj);
274             return;
275         }
276         JS_FreeValue(context, retVal);
277         JS_FreeValue(context, globalObj);
278     };
279     auto delegate = instance->GetDelegate();
280     if (!delegate) {
281         LOGE("delegate is null");
282         return JS_NULL;
283     }
284     delegate->PushJsCallbackToRenderNode(id, ratio, jsCallback);
285     return JS_NULL;
286 }
287 
JsObserverOff(JSContext * ctx,JSValueConst value,int32_t argc,JSValueConst * argv)288 JSValue ComponentApiBridge::JsObserverOff(JSContext* ctx, JSValueConst value, int32_t argc, JSValueConst* argv)
289 {
290     LOGD("ComponentApiBridge::JsObserverOff");
291     auto instance = static_cast<QjsEngineInstance*>(JS_GetContextOpaque(ctx));
292     if (!instance) {
293         LOGE("no instance");
294         return JS_NULL;
295     }
296     auto delegate = instance->GetDelegate();
297     if (!delegate) {
298         LOGE("delegate is null");
299         return JS_NULL;
300     }
301     NodeId id = 0;
302     JSValue nodeId = JS_GetPropertyStr(ctx, value, "__nodeId");
303     if (JS_IsInteger(nodeId) && (JS_ToInt32(ctx, &id, nodeId)) < 0) {
304         id = 0;
305     }
306     delegate->RemoveVisibleChangeNode(id);
307     JS_FreeValue(ctx, nodeId);
308     return JS_NULL;
309 }
310 
QJSVisibleListenerCallback(JSContext * ctx,JSValue callback,NodeId nodeId)311 QJSVisibleListenerCallback::QJSVisibleListenerCallback(JSContext* ctx, JSValue callback, NodeId nodeId)
312     : ctx_(ctx), listenCallback_(JS_DupValue(ctx, callback)), nodeId_(nodeId)
313 {}
314 
~QJSVisibleListenerCallback()315 QJSVisibleListenerCallback::~QJSVisibleListenerCallback()
316 {
317     // when last page exit, js engine will destruct first, so do not free JSObject again.
318     if (ctx_ != nullptr) {
319         JS_FreeValue(ctx_, listenCallback_);
320         ctx_ = nullptr;
321     }
322 }
323 
324 } // namespace OHOS::Ace::Framework
325