1 /*
2 * Copyright (c) 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 "frameworks/bridge/js_frontend/engine/v8/v8_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 #include "frameworks/bridge/js_frontend/engine/v8/v8_utils.h"
22
23 namespace OHOS::Ace::Framework {
24
JsGetScrollOffset(v8::Isolate * isolate,NodeId nodeId)25 v8::Local<v8::Object> V8ComponentApiBridge::JsGetScrollOffset(v8::Isolate* isolate, NodeId nodeId)
26 {
27 if (!isolate) {
28 return v8::Local<v8::Object>();
29 }
30 Offset offset;
31 auto page = static_cast<RefPtr<JsAcePage>*>(isolate->GetData(V8EngineInstance::RUNNING_PAGE));
32 if (page == nullptr) {
33 return v8::Local<v8::Object>();
34 }
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
62 auto delegate = static_cast<RefPtr<FrontendDelegate>*>(isolate->GetData(V8EngineInstance::FRONTEND_DELEGATE));
63 if (delegate == nullptr) {
64 return v8::Local<v8::Object>();
65 }
66 (*delegate)->PostSyncTaskToPage(task);
67
68 auto ctx = isolate->GetCurrentContext();
69 v8::Local<v8::Object> offsetContext = v8::Object::New(ctx->GetIsolate());
70 offsetContext
71 ->Set(ctx, v8::String::NewFromUtf8(ctx->GetIsolate(), "x").ToLocalChecked(),
72 v8::Number::New(ctx->GetIsolate(), offset.GetX()))
73 .ToChecked();
74 offsetContext
75 ->Set(ctx, v8::String::NewFromUtf8(ctx->GetIsolate(), "y").ToLocalChecked(),
76 v8::Number::New(ctx->GetIsolate(), offset.GetY()))
77 .ToChecked();
78 return offsetContext;
79 }
80
JsGetBoundingRect(v8::Isolate * isolate,NodeId nodeId)81 v8::Local<v8::Object> V8ComponentApiBridge::JsGetBoundingRect(v8::Isolate* isolate, NodeId nodeId)
82 {
83 if (!isolate) {
84 return v8::Local<v8::Object>();
85 }
86 auto delegate = static_cast<RefPtr<FrontendDelegate>*>(isolate->GetData(V8EngineInstance::FRONTEND_DELEGATE));
87 if (delegate == nullptr) {
88 return v8::Local<v8::Object>();
89 }
90
91 Rect boundingRect = (*delegate)->GetBoundingRectData(nodeId);
92 auto ctx = isolate->GetCurrentContext();
93 v8::Local<v8::Object> rectContext = v8::Object::New(ctx->GetIsolate());
94 rectContext
95 ->Set(ctx, v8::String::NewFromUtf8(ctx->GetIsolate(), "width").ToLocalChecked(),
96 v8::Number::New(ctx->GetIsolate(), boundingRect.Width()))
97 .ToChecked();
98 rectContext
99 ->Set(ctx, v8::String::NewFromUtf8(ctx->GetIsolate(), "height").ToLocalChecked(),
100 v8::Number::New(ctx->GetIsolate(), boundingRect.Height()))
101 .ToChecked();
102 rectContext
103 ->Set(ctx, v8::String::NewFromUtf8(ctx->GetIsolate(), "top").ToLocalChecked(),
104 v8::Number::New(ctx->GetIsolate(), boundingRect.Top()))
105 .ToChecked();
106 rectContext
107 ->Set(ctx, v8::String::NewFromUtf8(ctx->GetIsolate(), "left").ToLocalChecked(),
108 v8::Number::New(ctx->GetIsolate(), boundingRect.Left()))
109 .ToChecked();
110 return rectContext;
111 }
112
JsScrollTo(const v8::FunctionCallbackInfo<v8::Value> & args,const std::string & arguments,NodeId nodeId)113 void V8ComponentApiBridge::JsScrollTo(
114 const v8::FunctionCallbackInfo<v8::Value>& args, const std::string& arguments, NodeId nodeId)
115 {
116 v8::Isolate* isolate = args.GetIsolate();
117 if (isolate == nullptr) {
118 LOGE("V8ComponentApiBridge::JsScrollTo isolate is null!");
119 return;
120 }
121 v8::HandleScope handleScope(isolate);
122 auto context = isolate->GetCurrentContext();
123 if (context.IsEmpty()) {
124 LOGE("V8ComponentApiBridge::JsScrollTo context is empty!");
125 return;
126 }
127 v8::Local<v8::External> data = v8::Local<v8::External>::Cast(args.Data());
128 V8EngineInstance* engineInstance = static_cast<V8EngineInstance*>(data->Value());
129 if (engineInstance == nullptr) {
130 LOGE("V8ComponentApiBridge::JsScrollTo engineInstance is null!");
131 return;
132 }
133
134 auto page = static_cast<RefPtr<JsAcePage>*>(isolate->GetData(V8EngineInstance::RUNNING_PAGE));
135 if (page == nullptr) {
136 LOGE("V8ComponentApiBridge::JsScrollTo page is null");
137 return;
138 }
139
140 auto task = [nodeId, page, arguments]() {
141 auto domDoc = (*page)->GetDomDocument();
142 if (!domDoc) {
143 LOGE("V8ComponentApiBridge::JsScrollTo dom document is null!");
144 return;
145 }
146
147 auto domNode = domDoc->GetDOMNodeById(nodeId);
148 if (!domNode) {
149 LOGE("V8ComponentApiBridge::JsScrollTo node not exist!");
150 return;
151 }
152
153 std::unique_ptr<JsonValue> argsValue = JsonUtil::ParseJsonString(arguments);
154 if (!argsValue || !argsValue->IsArray() || argsValue->GetArraySize() < 1) {
155 LOGE("V8ComponentApiBridge::JsScrollTo parse args error");
156 return;
157 }
158 std::unique_ptr<JsonValue> scrollToPara = argsValue->GetArrayItem(0);
159 int32_t index = scrollToPara->GetInt("index", 0);
160 auto domList = AceType::DynamicCast<DOMList>(domNode);
161 if (domList) {
162 // list has specialized scrollTo method.
163 domList->ScrollToMethod(index);
164 return;
165 }
166
167 auto scrollComponent = domNode->GetScrollComponent();
168 if (!scrollComponent) {
169 return;
170 }
171 auto controller = scrollComponent->GetScrollPositionController();
172 if (!controller) {
173 return;
174 }
175
176 std::string id = scrollToPara->GetString("id", "");
177 double position = scrollToPara->GetDouble("position", 0.0);
178 double duration = scrollToPara->GetDouble("duration", 300.0); // Default duration is 300ms.
179 std::string timingFunction = scrollToPara->GetString("timingFunction", "ease");
180 std::string successId = scrollToPara->GetString("success", "");
181 std::string failId = scrollToPara->GetString("fail", "");
182 std::string completeId = scrollToPara->GetString("complete", "");
183 auto context = domNode->GetPipelineContext();
184 auto callback = [context, successId, completeId]() {
185 auto refContext = context.Upgrade();
186 if (refContext) {
187 refContext->SendCallbackMessageToFrontend(successId, std::string("\"success\",null"));
188 refContext->SendCallbackMessageToFrontend(completeId, std::string("\"complete\",null"));
189 }
190 };
191
192 bool result = false;
193 if (scrollToPara->Contains("position")) {
194 result = controller->AnimateTo(position, duration, CreateCurve(timingFunction), false, callback);
195 } else if (scrollToPara->Contains("id") && !id.empty()) {
196 result = controller->AnimateToTarget(id, duration, CreateCurve(timingFunction), false, callback);
197 } else {
198 LOGW("V8ComponentApiBridge::JsScrollTo param not valid.");
199 }
200 if (!result) {
201 auto refContext = context.Upgrade();
202 if (refContext) {
203 refContext->SendCallbackMessageToFrontend(failId, std::string("\"fail\",null"));
204 refContext->SendCallbackMessageToFrontend(completeId, std::string("\"complete\",null"));
205 }
206 }
207 };
208
209 auto delegate = engineInstance->GetDelegate();
210 if (!delegate) {
211 LOGE("V8ComponentApiBridge::JsScrollTo delegate is null");
212 return;
213 }
214 delegate->PostSyncTaskToPage(task);
215 }
216
JsAddVisibleListener(const v8::FunctionCallbackInfo<v8::Value> & args,const std::string & arguments,NodeId nodeId)217 v8::Local<v8::Object> V8ComponentApiBridge::JsAddVisibleListener(
218 const v8::FunctionCallbackInfo<v8::Value>& args, const std::string& arguments, NodeId nodeId)
219 {
220 v8::Isolate* isolate = args.GetIsolate();
221 if (isolate == nullptr) {
222 LOGE("V8ComponentApiBridge::JsAddVisibleListener isolate is null!");
223 return v8::Local<v8::Object>();
224 }
225 v8::HandleScope handleScope(isolate);
226 auto ctx = isolate->GetCurrentContext();
227 if (ctx.IsEmpty()) {
228 LOGE("V8ComponentApiBridge::JsAddVisibleListener context is empty!");
229 return v8::Local<v8::Object>();
230 }
231 std::unique_ptr<JsonValue> argsPtr = JsonUtil::ParseJsonString(arguments);
232 if (!argsPtr) {
233 LOGE("V8ComponentApiBridge::JsAddVisibleListener argsPtr is null!");
234 return v8::Local<v8::Object>();
235 }
236 auto observerParam = argsPtr->GetArrayItem(0);
237 if (!observerParam) {
238 return v8::Local<v8::Object>();
239 }
240 std::string type;
241 std::vector<double> ratios;
242 if (observerParam->GetValue("type") != nullptr && observerParam->GetValue("type")->IsString()) {
243 type = observerParam->GetValue("type")->GetString();
244 }
245 if (observerParam->GetValue("ratios") != nullptr && observerParam->GetValue("ratios")->IsArray()) {
246 auto ratioLen = observerParam->GetValue("ratios")->GetArraySize();
247 for (int32_t i = 0; i < ratioLen; ++i) {
248 auto ratioVal = observerParam->GetValue("ratios")->GetArrayItem(i);
249 if (ratioVal->IsNumber()) {
250 ratios.emplace_back(ratioVal->GetDouble());
251 }
252 }
253 }
254 auto listener = v8::Object::New(ctx->GetIsolate());
255 listener->Set(ctx, v8::String::NewFromUtf8(ctx->GetIsolate(), "__nodeId").ToLocalChecked(),
256 v8::Int32::New(ctx->GetIsolate(), nodeId)).ToChecked();
257 listener->Set(ctx, v8::String::NewFromUtf8(ctx->GetIsolate(), "__type").ToLocalChecked(),
258 v8::String::NewFromUtf8(ctx->GetIsolate(), type.c_str()).ToLocalChecked()).ToChecked();
259 for (int32_t j = 0; j < static_cast<int32_t>(ratios.size()); ++j) {
260 std::string ratioName = "__ratio" + std::to_string(j);
261 listener->Set(ctx, v8::String::NewFromUtf8(ctx->GetIsolate(), ratioName.c_str()).ToLocalChecked(),
262 v8::Number::New(ctx->GetIsolate(), ratios[j])).ToChecked();
263 }
264 listener->Set(ctx, v8::String::NewFromUtf8(ctx->GetIsolate(), "__ratioLen").ToLocalChecked(),
265 v8::Number::New(ctx->GetIsolate(), static_cast<int32_t>(ratios.size()))).ToChecked();
266 listener->Set(ctx, v8::String::NewFromUtf8(ctx->GetIsolate(), "observe").ToLocalChecked(),
267 v8::Function::New(ctx, JsVisibleListenerOn, v8::Local<v8::Value>(), 1).ToLocalChecked()).ToChecked();
268 listener->Set(ctx, v8::String::NewFromUtf8(ctx->GetIsolate(), "unobserve").ToLocalChecked(),
269 v8::Function::New(ctx, JsVisibleListenerOff, v8::Local<v8::Value>(), 0).ToLocalChecked()).ToChecked();
270 return listener;
271 }
272
JsVisibleListenerOn(const v8::FunctionCallbackInfo<v8::Value> & args)273 void V8ComponentApiBridge::JsVisibleListenerOn(const v8::FunctionCallbackInfo<v8::Value>& args)
274 {
275 v8::Isolate* isolate = args.GetIsolate();
276 v8::HandleScope handleScope(isolate);
277 auto context = isolate->GetCurrentContext();
278 auto observerValue = args.Holder();
279 NodeId id = observerValue->Get(context, v8::String::NewFromUtf8(isolate, "__nodeId").ToLocalChecked())
280 .ToLocalChecked()->Int32Value(context).ToChecked();
281 auto ratioLength = observerValue->Get(context, v8::String::NewFromUtf8(isolate, "__ratioLen").ToLocalChecked())
282 .ToLocalChecked()->NumberValue(context).ToChecked();
283 std::vector<double> ratios;
284 for (int32_t i = 0; i < ratioLength; ++i) {
285 auto ratioName = "__ratio" + std::to_string(i);
286 auto ratio = observerValue->Get(context, v8::String::NewFromUtf8(isolate, ratioName.c_str()).ToLocalChecked())
287 .ToLocalChecked()->NumberValue(context).ToChecked();
288 if (LessOrEqual(ratio, 0.0)) {
289 ratio = 0.001;
290 }
291 if (GreatOrEqual(ratio, 1.0)) {
292 ratio = 0.999;
293 }
294 ratios.emplace_back(ratio);
295 }
296
297 if (!args[0]->IsFunction()) {
298 LOGW("args is not function");
299 return;
300 }
301 v8::Local<v8::Function> jsFunc = v8::Local<v8::Function>::Cast(args[0]);
302 auto callbackObject = AceType::MakeRefPtr<VisibleListenerCallback>(context, isolate, jsFunc, id);
303 auto stagingPage = static_cast<RefPtr<JsAcePage>*>(isolate->GetData(V8EngineInstance::STAGING_PAGE));
304 int32_t instanceId = (*stagingPage)->GetPageId();
305 auto jsCallback = [callbackObj = callbackObject, isolate, instanceId](bool visible, double ratio) {
306 v8::HandleScope handleScope(isolate);
307 auto context = isolate->GetCurrentContext();
308 v8::Isolate::Scope isolateScope(isolate);
309 v8::Context::Scope contextScope(context);
310 v8::TryCatch tryCatch(isolate);
311 v8::Local<v8::Function> jsFunc = callbackObj->GetJsObject();
312
313 v8::Local<v8::Boolean> visibleJsVal = v8::Boolean::New(context->GetIsolate(), visible);
314 v8::Local<v8::Number> ratioJsVal = v8::Number::New(context->GetIsolate(), ratio);
315 v8::Local<v8::Value> argv[] = { visibleJsVal, ratioJsVal };
316 int32_t len = sizeof(argv) / sizeof(argv[0]);
317 v8::Local<v8::Value> res;
318 bool succ = jsFunc->Call(context, context->Global(), len, argv).ToLocal(&res);
319 if (!succ) {
320 LOGE("call jsFunc failed!");
321 V8Utils::JsStdDumpErrorAce(isolate, &tryCatch, JsErrorType::COMPILE_ERROR, instanceId);
322 }
323 };
324
325 auto delegate = static_cast<RefPtr<FrontendDelegate>*>(isolate->GetData(V8EngineInstance::FRONTEND_DELEGATE));
326 for (int32_t j = 0; j < static_cast<int32_t>(ratios.size()); ++j) {
327 (*delegate)->PushJsCallbackToRenderNode(id, ratios[j], jsCallback);
328 }
329 }
330
JsVisibleListenerOff(const v8::FunctionCallbackInfo<v8::Value> & args)331 void V8ComponentApiBridge::JsVisibleListenerOff(const v8::FunctionCallbackInfo<v8::Value>& args)
332 {
333 v8::Isolate* isolate = args.GetIsolate();
334 v8::HandleScope handleScope(isolate);
335 auto context = isolate->GetCurrentContext();
336 auto observerValue = args.Holder();
337 NodeId id = observerValue->Get(context, v8::String::NewFromUtf8(isolate, "__nodeId").ToLocalChecked())
338 .ToLocalChecked()->Int32Value(context).ToChecked();
339 auto delegate = static_cast<RefPtr<FrontendDelegate>*>(isolate->GetData(V8EngineInstance::FRONTEND_DELEGATE));
340 (*delegate)->RemoveVisibleChangeNode(id);
341 }
342
343 } // namespace OHOS::Ace::Framework