1 /*
2 * Copyright (c) 2025 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 "distributed_extension_js.h"
17
18 #include "distributed_extension_context_js.h"
19 #include "napi_common_want.h"
20
21 namespace OHOS {
22 namespace DistributedSchedule {
23 using namespace std;
24
25 constexpr size_t ARGC_ONE = 1;
26 static std::mutex g_distributedExtensionValidLock;
27 static int32_t g_distributedExtensionCount = 0;
28
GetSrcPath(const AppExecFwk::AbilityInfo & info)29 static string GetSrcPath(const AppExecFwk::AbilityInfo &info)
30 {
31 using AbilityRuntime::Extension;
32 stringstream ss;
33
34 if (!info.srcEntrance.empty()) {
35 ss << info.moduleName << '/' << string(info.srcEntrance, 0, info.srcEntrance.rfind(".")) << ".abc";
36 return ss.str();
37 }
38 return "";
39 }
40
AttachDistributedExtensionContext(napi_env env,void * value,void *)41 napi_value AttachDistributedExtensionContext(napi_env env, void *value, void *)
42 {
43 HILOG_INFO("AttachDistributedExtensionContext");
44 if (value == nullptr || env == nullptr) {
45 HILOG_WARN("invalid parameter.");
46 return nullptr;
47 }
48 auto ptr = reinterpret_cast<std::weak_ptr<DistributedExtensionContext> *>(value)->lock();
49 if (ptr == nullptr) {
50 HILOG_ERROR("invalid context.");
51 return nullptr;
52 }
53 auto object = CreateDistributedExtensionContextJS(env, ptr);
54 if (object == nullptr) {
55 HILOG_ERROR("Failed to get js distributed extension context");
56 return nullptr;
57 }
58 auto contextRef = AbilityRuntime::JsRuntime::LoadSystemModuleByEngine(env,
59 "application.DistributedExtensionContext", &object, 1);
60 if (contextRef == nullptr) {
61 HILOG_ERROR("Failed to load BackupExtensionContext.");
62 return nullptr;
63 }
64 napi_value contextObj = contextRef->GetNapiValue();
65 napi_coerce_to_native_binding_object(env, contextObj, AbilityRuntime::DetachCallbackFunc,
66 AttachDistributedExtensionContext, value, nullptr);
67
68 auto workContext = new (std::nothrow) std::weak_ptr<DistributedExtensionContext>(ptr);
69 if (workContext == nullptr) {
70 HILOG_ERROR("Failed to get backup extension context");
71 return nullptr;
72 }
73 napi_status status = napi_wrap(
74 env, contextObj, workContext,
75 [](napi_env, void *data, void *) {
76 HILOG_DEBUG("Finalizer for weak_ptr base context is called");
77 delete static_cast<std::weak_ptr<DistributedExtensionContext> *>(data);
78 },
79 nullptr, nullptr);
80 if (status != napi_ok) {
81 HILOG_DEBUG("Failed to wrap js instance");
82 delete workContext;
83 workContext = nullptr;
84 }
85 return contextObj;
86 }
87
Init(const shared_ptr<AppExecFwk::AbilityLocalRecord> & record,const shared_ptr<AppExecFwk::OHOSApplication> & application,shared_ptr<AppExecFwk::AbilityHandler> & handler,const sptr<IRemoteObject> & token)88 void DistributedExtensionJs::Init(const shared_ptr<AppExecFwk::AbilityLocalRecord> &record,
89 const shared_ptr<AppExecFwk::OHOSApplication> &application,
90 shared_ptr<AppExecFwk::AbilityHandler> &handler, const sptr<IRemoteObject> &token)
91 {
92 HILOG_INFO("Init the DistributedExtensionAbility(JS)");
93 try {
94 DistributedExtension::Init(record, application, handler, token);
95 if (abilityInfo_ == nullptr) {
96 HILOG_ERROR("Invalid abilityInfo_");
97 return;
98 }
99 const AppExecFwk::AbilityInfo &info = *abilityInfo_;
100 string bundleName = info.bundleName;
101 string moduleName(info.moduleName + "::" + info.name);
102 string modulePath = GetSrcPath(info);
103 int moduleType = static_cast<int>(info.type);
104 HILOG_INFO("Try to load %{public}s's %{public}s(type %{public}d) from %{public}s", bundleName.c_str(),
105 moduleName.c_str(), moduleType, modulePath.c_str());
106
107 AbilityRuntime::HandleScope handleScope(jsRuntime_);
108 jsObj_ = jsRuntime_.LoadModule(moduleName, modulePath, info.hapPath,
109 abilityInfo_->compileMode == AbilityRuntime::CompileMode::ES_MODULE);
110 if (jsObj_ == nullptr) {
111 HILOG_WARN("Oops! There's no custom DistributedExtensionAbility");
112 return;
113 }
114 HILOG_INFO("Wow! Here's a custsom DistributedExtensionAbility");
115 ExportJsContext();
116 } catch (const exception &e) {
117 HILOG_ERROR("%{public}s", e.what());
118 }
119 }
120
DistributedExtensionJs(AbilityRuntime::JsRuntime & jsRuntime)121 DistributedExtensionJs::DistributedExtensionJs(AbilityRuntime::JsRuntime &jsRuntime) : jsRuntime_(jsRuntime)
122 {
123 std::lock_guard<std::mutex> lock(g_distributedExtensionValidLock);
124 g_distributedExtensionCount += 1;
125 HILOG_INFO("DistributedExtensionJs::DistributedExtensionJs, count=%{public}d.", g_distributedExtensionCount);
126 }
127
~DistributedExtensionJs()128 DistributedExtensionJs::~DistributedExtensionJs()
129 {
130 jsRuntime_.FreeNativeReference(std::move(jsObj_));
131 std::lock_guard<std::mutex> lock(g_distributedExtensionValidLock);
132 g_distributedExtensionCount -= 1;
133 HILOG_INFO("DistributedExtensionJs::~DistributedExtensionJs, count=%{public}d.", g_distributedExtensionCount);
134 }
135
ExportJsContext(void)136 void DistributedExtensionJs::ExportJsContext(void)
137 {
138 auto env = jsRuntime_.GetNapiEnv();
139 if (jsObj_ == nullptr) {
140 HILOG_ERROR("Failed to get js object.");
141 return;
142 }
143 napi_value obj = jsObj_->GetNapiValue();
144 if (obj == nullptr) {
145 HILOG_ERROR("Failed to get DistributedExtensionAbility object");
146 return;
147 }
148
149 auto context = GetContext();
150 if (context == nullptr) {
151 HILOG_ERROR("Failed to get context");
152 return;
153 }
154
155 HILOG_INFO("CreateDistributedExtensionContextJS");
156 napi_value contextObj = CreateDistributedExtensionContextJS(env, context);
157 auto contextRef = jsRuntime_.LoadSystemModule("application.DistributedExtensionContext", &contextObj, ARGC_ONE);
158 if (!contextRef) {
159 HILOG_ERROR("context is nullptr");
160 return;
161 }
162 contextObj = contextRef->GetNapiValue();
163 HILOG_INFO("Bind context");
164 context->Bind(jsRuntime_, contextRef.release());
165 napi_set_named_property(env, obj, "context", contextObj);
166
167 auto workContext = new (std::nothrow) std::weak_ptr<DistributedExtensionContext>(context);
168 if (workContext == nullptr) {
169 HILOG_ERROR("Failed to create DistributedExtensionContext.");
170 return;
171 }
172 napi_coerce_to_native_binding_object(env, contextObj, AbilityRuntime::DetachCallbackFunc,
173 AttachDistributedExtensionContext, workContext, nullptr);
174 HILOG_INFO("Set backup extension ability context pointer is nullptr: %{public}d", context.get() == nullptr);
175 napi_status status = napi_wrap(
176 env, contextObj, workContext,
177 [](napi_env, void *data, void *) {
178 HILOG_INFO("Finalizer for weak_ptr base context is called");
179 delete static_cast<std::weak_ptr<DistributedExtensionContext> *>(data);
180 },
181 nullptr, nullptr);
182 if (status != napi_ok) {
183 HILOG_INFO("Failed to wrap js instance");
184 delete workContext;
185 workContext = nullptr;
186 }
187 }
188
Create(const unique_ptr<AbilityRuntime::Runtime> & runtime)189 DistributedExtensionJs *DistributedExtensionJs::Create(const unique_ptr<AbilityRuntime::Runtime> &runtime)
190 {
191 HILOG_INFO("Create as an DistributedExtensionAbility(JS)");
192 return new DistributedExtensionJs(static_cast<AbilityRuntime::JsRuntime &>(*runtime));
193 }
194
TriggerOnCreate(AAFwk::Want & want)195 int32_t DistributedExtensionJs::TriggerOnCreate(AAFwk::Want& want)
196 {
197 HILOG_INFO("DistributedExtensionAbility(JS) TriggerOnCreate ex");
198 want_ = want;
199 if (jsObj_ == nullptr) {
200 HILOG_ERROR("The app does not provide the TriggerOnCreate interface.");
201 }
202
203 return CallJsOnCreate();
204 }
205
CallJsOnCreate()206 int32_t DistributedExtensionJs::CallJsOnCreate()
207 {
208 HILOG_INFO("Start call app js method onCreate");
209 auto errCode = CallJsMethod("onCreate", jsRuntime_, jsObj_.get(), ParseCreateInfo(), nullptr);
210 if (errCode != ERR_OK) {
211 HILOG_ERROR("CallJsMethod error, code:%{public}d.", errCode);
212 }
213 return errCode;
214 }
215
ParseCreateInfo()216 std::function<bool(napi_env env, std::vector<napi_value> &argv)> DistributedExtensionJs::ParseCreateInfo()
217 {
218 auto onCreateFun = [want(want_)](napi_env env, vector<napi_value> &argv) -> bool {
219 napi_value napiWant = OHOS::AppExecFwk::WrapWant(env, want);
220 argv.push_back(napiWant);
221 return true;
222 };
223 return onCreateFun;
224 }
225
TriggerOnDestroy()226 int32_t DistributedExtensionJs::TriggerOnDestroy()
227 {
228 HILOG_INFO("DistributedExtensionAbility(JS) TriggerOnDestroy ex");
229 if (jsObj_ == nullptr) {
230 HILOG_ERROR("The app does not provide the TriggerOnDestroy interface.");
231 }
232
233 return CallJsOnDestroy();
234 }
235
CallJsOnDestroy()236 int32_t DistributedExtensionJs::CallJsOnDestroy()
237 {
238 HILOG_INFO("Start call app js method onDestroy");
239 auto errCode = CallJsMethod("onDestroy", jsRuntime_, jsObj_.get(), nullptr, nullptr);
240 if (errCode != ERR_OK) {
241 HILOG_ERROR("CallJsMethod error, code:%{public}d.", errCode);
242 }
243 return errCode;
244 }
245
TriggerOnCollaborate(AAFwk::WantParams & wantParam)246 int32_t DistributedExtensionJs::TriggerOnCollaborate(AAFwk::WantParams &wantParam)
247 {
248 HILOG_INFO("jDistributedExtensionAbility(JS) TriggerOnCollaborate ex");
249 wantParam_ = wantParam;
250 if (jsObj_ == nullptr) {
251 HILOG_ERROR("The app does not provide the TriggerOnCollaborate interface.");
252 }
253
254 return CallJsOnCollaborate();
255 }
256
CallJsOnCollaborate()257 int32_t DistributedExtensionJs::CallJsOnCollaborate()
258 {
259 HILOG_INFO("Start call app js method OnCollaborate");
260 auto retParser = [jsRuntime{ &jsRuntime_ }](napi_env env,
261 napi_value result) -> bool {
262 uint32_t value;
263 if (napi_get_value_uint32(env, result, &value) != napi_ok) {
264 HILOG_ERROR("Can not get uint32 value");
265 return false;
266 }
267
268 HILOG_INFO("CallJsOnCollaborate result = %{public}d.", value);
269 return true;
270 };
271 auto errCode = CallJsMethod("onCollaborate", jsRuntime_, jsObj_.get(), ParseCollabInfo(), retParser);
272 if (errCode != ERR_OK) {
273 HILOG_ERROR("CallJsMethod error, code:%{public}d.", errCode);
274 }
275 return errCode;
276 }
277
ParseCollabInfo()278 std::function<bool(napi_env env, std::vector<napi_value> &argv)> DistributedExtensionJs::ParseCollabInfo()
279 {
280 auto onCollabFun = [wantParam(wantParam_)](napi_env env, vector<napi_value> &argv) -> bool {
281 napi_value napiWantParam = AppExecFwk::WrapWantParams(env, wantParam);
282 argv.push_back(napiWantParam);
283 return true;
284 };
285 return onCollabFun;
286 }
287
InvokeJsMethod(CallJsParam * param,AbilityRuntime::HandleEscape & handleEscape,napi_env env,napi_handle_scope & scope,vector<napi_value> & argv)288 static int InvokeJsMethod(CallJsParam *param, AbilityRuntime::HandleEscape& handleEscape, napi_env env,
289 napi_handle_scope& scope, vector<napi_value>& argv)
290 {
291 HILOG_INFO("InvokeJsMethod start");
292 if (param == nullptr || param->jsObj == nullptr) {
293 HILOG_ERROR("param or jsObj is nullptr");
294 return EINVAL;
295 }
296 napi_value value = param->jsObj->GetNapiValue();
297 if (value == nullptr) {
298 HILOG_ERROR("failed to get napi value object.");
299 return EINVAL;
300 }
301 napi_status status;
302 napi_value method;
303 status = napi_get_named_property(env, value, param->funcName.c_str(), &method);
304 if (status != napi_ok) {
305 HILOG_ERROR("napi_get_named_property failed.");
306 return EINVAL;
307 }
308 napi_value result;
309 HILOG_INFO("Extension start do call current js method, methodName:%{public}s", param->funcName.c_str());
310 if (napi_call_function(env, value, method, argv.size(), argv.data(), &result) != napi_ok) {
311 HILOG_ERROR("napi call function failed");
312 }
313 if (param->retParser != nullptr) {
314 if (!param->retParser(env, handleEscape.Escape(result))) {
315 HILOG_ERROR("Parser js result fail.");
316 return EINVAL;
317 }
318 }
319 HILOG_INFO("InvokeJsMethod end");
320 return ERR_OK;
321 }
322
DoCallJsMethod(CallJsParam * param)323 static int DoCallJsMethod(CallJsParam *param)
324 {
325 HILOG_INFO("DoCallJsMethod enter");
326 if (param == nullptr) {
327 HILOG_ERROR("param is nullptr");
328 return EINVAL;
329 }
330 AbilityRuntime::JsRuntime *jsRuntime = param->jsRuntime;
331 HILOG_INFO("Start execute DoCallJsMethod");
332 if (jsRuntime == nullptr) {
333 HILOG_ERROR("failed to get jsRuntime");
334 return EINVAL;
335 }
336 AbilityRuntime::HandleEscape handleEscape(*jsRuntime);
337 auto env = jsRuntime->GetNapiEnv();
338 napi_handle_scope scope = nullptr;
339 napi_open_handle_scope(env, &scope);
340 if (scope == nullptr) {
341 HILOG_ERROR("scope is nullptr");
342 return EINVAL;
343 }
344 vector<napi_value> argv = {};
345 if (param->argParser != nullptr) {
346 if (!param->argParser(env, argv)) {
347 HILOG_ERROR("failed to get params.");
348 napi_close_handle_scope(env, scope);
349 return EINVAL;
350 }
351 }
352 auto ret = InvokeJsMethod(param, handleEscape, env, scope, argv);
353 napi_close_handle_scope(env, scope);
354 HILOG_INFO("End execute DoCallJsMethod");
355 return ret;
356 }
357
CallJsMethod(const std::string & funcName,AbilityRuntime::JsRuntime & jsRuntime,NativeReference * jsObj,InputArgsParser argParser,ResultValueParser retParser)358 int32_t DistributedExtensionJs::CallJsMethod(const std::string &funcName, AbilityRuntime::JsRuntime &jsRuntime,
359 NativeReference *jsObj, InputArgsParser argParser, ResultValueParser retParser)
360 {
361 HILOG_INFO("DistributedExtensionJs::CallJsMethod enter");
362 uv_loop_s *loop = nullptr;
363 napi_status status = napi_get_uv_event_loop(jsRuntime.GetNapiEnv(), &loop);
364 if (status != napi_ok) {
365 HILOG_ERROR("failed to get uv event loop.");
366 return EINVAL;
367 }
368 auto param = std::make_shared<CallJsParam>(funcName, &jsRuntime, jsObj, argParser, retParser);
369 if (param == nullptr) {
370 HILOG_ERROR("failed to new param.");
371 return EINVAL;
372 }
373
374 auto work = std::make_shared<uv_work_t>();
375 if (work == nullptr) {
376 HILOG_ERROR("failed to new uv_work_t.");
377 return EINVAL;
378 }
379
380 work->data = reinterpret_cast<void *>(param.get());
381 int ret = uv_queue_work(
382 loop, work.get(), [](uv_work_t *work) {
383 HILOG_INFO("Enter, %{public}zu", (size_t)work);
384 },
385 [](uv_work_t *work, int status) {
386 HILOG_INFO("AsyncWork Enter, %{public}zu", (size_t)work);
387 CallJsParam *param = reinterpret_cast<CallJsParam *>(work->data);
388 do {
389 if (param == nullptr) {
390 HILOG_ERROR("failed to get CallJsParam.");
391 break;
392 }
393 HILOG_INFO("Start call current js method");
394 if (DoCallJsMethod(param) != ERR_OK) {
395 HILOG_ERROR("failed to call DoCallJsMethod.");
396 }
397 } while (false);
398 HILOG_INFO("will notify current thread info");
399 std::unique_lock<std::mutex> lock(param->distributedOperateMutex);
400 param->isReady.store(true);
401 param->distributedOperateCondition.notify_all();
402 });
403 if (ret != 0) {
404 HILOG_ERROR("failed to exec uv_queue_work.");
405 return EINVAL;
406 }
407 std::unique_lock<std::mutex> lock(param->distributedOperateMutex);
408 param->distributedOperateCondition.wait(lock, [param]() { return param->isReady.load(); });
409 HILOG_INFO("End do call current js method");
410 return ERR_OK;
411 }
412 }
413 }
414