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