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