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 "core/components_ng/pattern/web/web_data_detector_adapter.h"
17
18 #include <algorithm>
19 #include <string>
20 #include <vector>
21
22 #include "adapter/ohos/entrance/ace_container.h"
23 #include "base/utils/utf_helper.h"
24 #include "base/utils/utils.h"
25 #include "core/common/ai/data_detector_mgr.h"
26 #include "core/components_ng/pattern/rich_editor_drag/preview_menu_controller.h"
27 #include "core/components_ng/pattern/web/web_pattern.h"
28 #include "core/components/text_overlay/text_overlay_theme.h"
29 #include "core/components/web/resource/web_delegate.h"
30 #include "core/pipeline_ng/pipeline_context.h"
31
32
33 namespace OHOS::Ace::NG {
34 namespace {
35 const std::string SEPARATE_STRING = "\n";
36 const size_t SEP_LENGTH = 1;
37 const size_t MAX_DETECT_LENGTH = (1 << 30);
38
39 const std::string JS_DATA_DETECTOR = "arkWebEntityReplacer";
40 const std::string JS_DATA_DETECTOR_METHOD = "handleNativeResult";
41 const std::string DATA_DETECTOR_PROXY = "arkWebAceEntityReplacerProxy";
42 const std::string PROXY_METHOD_PROCESS_REQUEST = "processRequest";
43 const std::string PROXY_METHOD_CLICK_ENTITY = "clickEntity";
44
45 const std::string ALL_TEXT_DETECT_TYPES = "phoneNum,url,email,location,datetime";
46
47 const std::vector<std::string> TEXT_DETECT_LIST = {
48 "phoneNum" ,"url" ,"email", "location", "datetime"
49 };
50
51 const std::unordered_map<std::string, std::string> TEXT_DETECT_MAP_TO_HREF = {
52 { "phoneNum", "tel:" }, { "url", "" },
53 { "email", "mailto:" }, { "location", "geo:" },
54 { "datetime", "webcal:" }
55 };
56
57 const std::unordered_map<TextDecoration, std::string> TEXT_DECORATION_MAP = {
58 {TextDecoration::NONE, ""},
59 {TextDecoration::UNDERLINE, "underline"},
60 {TextDecoration::OVERLINE, "overline"},
61 {TextDecoration::LINE_THROUGH, "line-through"},
62 {TextDecoration::INHERIT, "inherit"},
63 };
64
65 const std::unordered_map<TextDecorationStyle, std::string> TEXT_DECORATION_STYLE_MAP = {
66 {TextDecorationStyle::SOLID, "solid"},
67 {TextDecorationStyle::DOUBLE, "double"},
68 {TextDecorationStyle::DOTTED, "dotted"},
69 {TextDecorationStyle::DASHED, "dashed"},
70 {TextDecorationStyle::WAVY, "wavy"},
71 {TextDecorationStyle::INITIAL, "initial"},
72 {TextDecorationStyle::INHERIT, "inherit"},
73 };
74
75 constexpr char COPY[] = "copy";
76 constexpr char SELECT_TEXT[] = "selectText";
77
78 constexpr Dimension PREVIEW_MENU_MARGIN_LEFT = 16.0_vp;
79 constexpr Dimension PREVIEW_MENU_MARGIN_RIGHT = 16.0_vp;
80 constexpr Dimension MENU_WIDTH = 224.0_vp;
81 } // namespace
82
WebDataDetectorAdapter(const WeakPtr<Pattern> & pattern,size_t cacheSize)83 WebDataDetectorAdapter::WebDataDetectorAdapter(const WeakPtr<Pattern>& pattern, size_t cacheSize)
84 {
85 pattern_ = pattern;
86 if (cacheSize > 0) {
87 resultCache_ = AceType::MakeRefPtr<WebDataDetectorCache<std::string, DataDetectorResult>>(cacheSize);
88 }
89 TextDetectConfig defaultConfig;
90 defaultConfig.types = ALL_TEXT_DETECT_TYPES;
91 SetDataDetectorConfig(defaultConfig);
92 }
93
SetDataDetectorEnable(bool enable)94 void WebDataDetectorAdapter::SetDataDetectorEnable(bool enable)
95 {
96 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::SetDataDetectorEnable: %{public}d", enable);
97 newConfig_.enable = enable;
98 }
99
SetDataDetectorConfig(const TextDetectConfig & config)100 void WebDataDetectorAdapter::SetDataDetectorConfig(const TextDetectConfig& config)
101 {
102 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::UpdateDataDetectorConfig");
103 newConfig_.types = config.types.empty() ? ALL_TEXT_DETECT_TYPES : config.types;
104 newConfig_.color = config.entityColor.ToString();
105 TAG_LOGD(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::UpdateDataDetectorConfig dataDetectType_ : %{public}s",
106 newConfig_.types.c_str());
107
108 auto itType = TEXT_DECORATION_MAP.find(config.entityDecorationType);
109 auto itStyle = TEXT_DECORATION_STYLE_MAP.find(config.entityDecorationStyle);
110
111 newConfig_.textDecorationStyle = config.entityDecorationColor.ToString() + " " +
112 (itType != TEXT_DECORATION_MAP.end() ? itType->second : "") + " " +
113 (itStyle != TEXT_DECORATION_STYLE_MAP.end() ? itStyle->second : "");
114
115 TAG_LOGD(AceLogTag::ACE_WEB,
116 "WebDataDetectorAdapter::UpdateDataDetectorConfig dataDetectTextDecorationStyle_ : %{public}s",
117 newConfig_.textDecorationStyle.c_str());
118 newConfig_.enablePreview = config.enablePreviewMenu;
119 TAG_LOGD(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::UpdateDataDetectorConfig enablePreview : %{public}d",
120 newConfig_.enablePreview);
121 }
122
Init()123 void WebDataDetectorAdapter::Init()
124 {
125 if (!IsAISupported()) {
126 return;
127 }
128 bool flag = config_.enable != newConfig_.enable || !hasInit_;
129 config_ = newConfig_;
130 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::Init config changed = %{public}d, enable = %{public}d", flag,
131 config_.enable);
132 ResetContextMap();
133 if (!flag) {
134 return;
135 }
136 if (config_.enable) {
137 InitJSProxy();
138 InitAIMenu();
139 } else {
140 ReleaseJSProxy();
141 }
142 SetNWebConfig();
143 hasInit_ = true;
144 }
145
InitJSProxy()146 void WebDataDetectorAdapter::InitJSProxy()
147 {
148 if (initDataDetectorProxy_) {
149 return;
150 }
151 auto pattern = DynamicCast<WebPattern>(pattern_.Upgrade());
152 CHECK_NULL_VOID(pattern);
153 auto webId = pattern->GetWebId();
154 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::InitJSProxy WebId: %{public}d", webId);
155 if (webId < 0) {
156 return;
157 }
158 auto instanceId = Container::CurrentIdSafely();
159 std::vector<std::string> methods = { PROXY_METHOD_PROCESS_REQUEST, PROXY_METHOD_CLICK_ENTITY };
160 std::vector<std::function<void(const std::vector<std::string>&)>> funcs = {
161 [weak = AceType::WeakClaim(this), instanceId](const std::vector<std::string>& param) {
162 ContainerScope scope(instanceId);
163 auto adapter = weak.Upgrade();
164 if (adapter && param.size() > 0) {
165 adapter->ProcessRequest(param[0]);
166 }
167 },
168 [weak = AceType::WeakClaim(this), instanceId](const std::vector<std::string>& param) {
169 ContainerScope scope(instanceId);
170 auto adapter = weak.Upgrade();
171 if (adapter && param.size() > 0) {
172 adapter->ProcessClick(param[0]);
173 }
174 }
175 };
176 auto delegate = pattern->delegate_;
177 CHECK_NULL_VOID(delegate);
178 delegate->RegisterNativeJavaScriptProxy(DATA_DETECTOR_PROXY, methods, funcs, false, "", hasInit_);
179 initDataDetectorProxy_ = true;
180 }
181
ReleaseJSProxy()182 void WebDataDetectorAdapter::ReleaseJSProxy()
183 {
184 if (!initDataDetectorProxy_) {
185 return;
186 }
187 auto pattern = DynamicCast<WebPattern>(pattern_.Upgrade());
188 CHECK_NULL_VOID(pattern);
189 auto webId = pattern->GetWebId();
190 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::ReleaseJSProxy WebId: %{public}d", webId);
191 if (webId < 0) {
192 return;
193 }
194 auto delegate = pattern->delegate_;
195 CHECK_NULL_VOID(delegate);
196 delegate->UnRegisterNativeArkJSFunction(DATA_DETECTOR_PROXY); // with stability problems
197 initDataDetectorProxy_ = false;
198 }
199
SetNWebConfig()200 void WebDataDetectorAdapter::SetNWebConfig()
201 {
202 auto pattern = DynamicCast<WebPattern>(pattern_.Upgrade());
203 CHECK_NULL_VOID(pattern);
204 auto webId = pattern->GetWebId();
205 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::SetNWebConfig WebId: %{public}d", webId);
206 if (webId < 0) {
207 return;
208 }
209 auto delegate = pattern->delegate_;
210 CHECK_NULL_VOID(delegate);
211 delegate->SetDataDetectorEnable(config_.enable);
212 }
213
ProcessRequest(const std::string & jsonStr)214 void WebDataDetectorAdapter::ProcessRequest(const std::string& jsonStr)
215 {
216 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::ProcessRequest start");
217 if (!GetDataDetectorEnable()) {
218 return;
219 }
220 auto requestJson = JsonUtil::ParseJsonString(jsonStr);
221 if (!requestJson || !requestJson->IsObject()) {
222 return;
223 }
224 auto requestId = requestJson->GetString("requestId");
225 if (requestId.empty()) {
226 return;
227 }
228 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::ProcessRequest requestId: %{public}s", requestId.c_str());
229 DataDetectorRequestData requestData;
230 requestData.requestId = requestId;
231 auto nodesValue = requestJson->GetValue("nodes");
232 if (nodesValue && nodesValue->IsArray()) {
233 int32_t arraySize = nodesValue->GetArraySize();
234 for (int32_t i = 0; i < arraySize; ++i) {
235 auto nodeValue = nodesValue->GetArrayItem(i);
236 if (!nodeValue || !nodeValue->IsObject()) {
237 continue;
238 }
239 NodeData nodeData;
240 nodeData.path = nodeValue->GetString("path");
241 nodeData.text = nodeValue->GetString("text");
242 requestData.nodes.emplace_back(nodeData);
243 }
244 }
245 TAG_LOGD(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::ProcessRequest request node size: %{public}zu",
246 requestData.nodes.size());
247 if (requestData.nodes.empty()) {
248 TAG_LOGE(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::ProcessRequest request node empty");
249 return;
250 }
251 SetRequestContext(requestId, std::move(requestData));
252 AIPostTask(
253 [weak = AceType::WeakClaim(this), requestId]() {
254 auto adapter = weak.Upgrade();
255 CHECK_NULL_VOID(adapter);
256 adapter->SendRequestToAI(requestId);
257 },
258 TaskExecutor::TaskType::UI, "SendRequestToAI");
259 }
260
PrepareDetectText(const std::string & requestId)261 std::string WebDataDetectorAdapter::PrepareDetectText(const std::string& requestId)
262 {
263 TAG_LOGD(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::PrepareContext");
264 auto requestContext = GetRequestContext(requestId);
265 CHECK_NULL_RETURN(requestContext, "");
266 size_t index = 0;
267 std::vector<size_t> detectIds;
268 std::string detectText = "";
269 // u16 pair list
270 std::vector<std::pair<size_t, size_t> > detectOffsets;
271 size_t maxDetectLength = 0;
272 std::vector<DataDetectorResult> matches(requestContext->nodes.size());
273 for (const auto& node : requestContext->nodes) {
274 DataDetectorResult result;
275 if (resultCache_ && resultCache_->Get(node.text, result)) {
276 matches[index] = result; // overwrite
277 } else {
278 detectIds.emplace_back(index);
279 detectText += node.text + SEPARATE_STRING;
280 auto wText = UtfUtils::Str8DebugToStr16(node.text);
281 detectOffsets.emplace_back(maxDetectLength, maxDetectLength + wText.size());
282 maxDetectLength += wText.size() + SEP_LENGTH;
283 }
284 ++index;
285 }
286 requestContext->matches = std::move(matches);
287 requestContext->detectIds = std::move(detectIds);
288 requestContext->detectOffsets = std::move(detectOffsets);
289 return detectText;
290 }
291
SendRequestToAI(const std::string & requestId)292 void WebDataDetectorAdapter::SendRequestToAI(const std::string& requestId)
293 {
294 TAG_LOGD(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::SendRequestToAI");
295 // ui thread
296
297 auto instanceId = Container::CurrentIdSafely();
298 auto resultFunc = [weak = AceType::WeakClaim(this), requestId, instanceId](const TextDataDetectResult result) {
299 // background thread
300 ContainerScope scope(instanceId);
301 auto adapter = weak.Upgrade();
302 CHECK_NULL_VOID(adapter);
303 adapter->HandleResultFromAI(requestId, result);
304 };
305
306 std::string detectText = PrepareDetectText(requestId);
307 if (detectText.empty()) {
308 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::SendRequestToAI detectText empty");
309 AIPostTask(
310 [resultFunc]() {
311 TextDataDetectResult emptyResult;
312 emptyResult.entity = "{}";
313 resultFunc(emptyResult);
314 },
315 TaskExecutor::TaskType::BACKGROUND, "NoDataDetect");
316 return;
317 }
318
319 TextDataDetectInfo info { detectText, config_.types };
320 AIPostTask(
321 [info, resultFunc]() {
322 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::SendRequestToAI start AI detect, length: %{public}zu",
323 info.text.size());
324 DataDetectorMgr::GetInstance().DataDetect(info, resultFunc);
325 },
326 TaskExecutor::TaskType::BACKGROUND, "DataDetect");
327 }
328
ParseAIResultByType(std::shared_ptr<DataDetectorRequestData> & requestContext,const std::string & detectType,const std::unique_ptr<JsonValue> & jsonValue)329 void WebDataDetectorAdapter::ParseAIResultByType(std::shared_ptr<DataDetectorRequestData>& requestContext,
330 const std::string& detectType, const std::unique_ptr<JsonValue>& jsonValue)
331 {
332 CHECK_NULL_VOID(requestContext);
333 int32_t arraySize = jsonValue->GetArraySize();
334 for (int32_t i = 0; i < arraySize; ++i) {
335 auto item = jsonValue->GetArrayItem(i);
336 if (!item || !item->IsObject()) {
337 continue;
338 }
339 auto charOffset = item->GetInt("charOffset");
340 if (charOffset < 0) {
341 TAG_LOGE(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::ParseAIResultByType charOffset invalid");
342 continue;
343 }
344 EntityMatch mat;
345 mat.start = static_cast<size_t>(charOffset); // u16 offset
346 mat.clean = item->GetString("oriText"); // u8 string
347 mat.end = mat.start + UtfUtils::Str8DebugToStr16(mat.clean).length(); // u16 offset not include
348 mat.entityType = detectType;
349 mat.params = ParseExtraParams(detectType, item);
350 int pos = MatchInOffsets(mat, requestContext->detectOffsets);
351 if (pos < 0) {
352 continue;
353 }
354 size_t id = requestContext->detectIds[pos];
355 if (id >= requestContext->matches.size()) {
356 continue;
357 }
358 requestContext->matches[id].emplace_back(std::move(mat));
359 }
360 }
361
HandleResultFromAI(const std::string & requestId,const TextDataDetectResult & result)362 void WebDataDetectorAdapter::HandleResultFromAI(const std::string& requestId, const TextDataDetectResult& result)
363 {
364 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::HandleResultFromAI receive AI result");
365 // background thread
366 auto entityJson = JsonUtil::ParseJsonString(result.entity);
367 if (!entityJson || !entityJson->IsObject()) {
368 TAG_LOGE(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::HandleResultFromAI entityJson invalid");
369 entityJson = JsonUtil::ParseJsonString("{}");
370 }
371 auto requestContext = GetRequestContext(requestId);
372 CHECK_NULL_VOID(requestContext);
373 for (const auto& detectType : TEXT_DETECT_LIST) {
374 if (auto jsonValue = entityJson->GetValue(detectType)) {
375 if (!jsonValue->IsArray()) {
376 continue;
377 }
378 ParseAIResultByType(requestContext, detectType, jsonValue);
379 }
380 }
381 for (auto id : requestContext->detectIds) {
382 if (id >= requestContext->matches.size()) {
383 continue;
384 }
385 auto& nodeResult = requestContext->matches[id]; // overwrite
386 std::sort(nodeResult.begin(), nodeResult.end(), [](const EntityMatch& a, const EntityMatch& b) {
387 return a.start < b.start;
388 });
389 if (resultCache_) {
390 resultCache_->Put(requestContext->nodes[id].text, nodeResult);
391 }
392 }
393 auto resultStr = GetResultJsonString(requestId);
394 RemoveRequestContext(requestId);
395 AIPostTask(
396 [weak = AceType::WeakClaim(this), resultStr]() {
397 auto adapter = weak.Upgrade();
398 CHECK_NULL_VOID(adapter);
399 adapter->SendResultToJS(resultStr);
400 },
401 TaskExecutor::TaskType::UI, "SendResultToJS");
402 }
403
SendResultToJS(const std::string & resultStr)404 void WebDataDetectorAdapter::SendResultToJS(const std::string& resultStr)
405 {
406 auto jsCode = JS_DATA_DETECTOR + "." + JS_DATA_DETECTOR_METHOD + "('" + resultStr + "')";
407 auto pattern = DynamicCast<WebPattern>(pattern_.Upgrade());
408 CHECK_NULL_VOID(pattern);
409 pattern->RunJavascriptAsync(jsCode, [](std::string result) {});
410 }
411
AttrsToParams(const std::unique_ptr<JsonValue> & attrs)412 std::map<std::string, std::string> WebDataDetectorAdapter::AttrsToParams(const std::unique_ptr<JsonValue>& attrs)
413 {
414 if (!attrs || !attrs->IsObject()) {
415 return {};
416 }
417 std::map<std::string, std::string> params;
418 for (auto key : extraParamKeys_) {
419 auto value = attrs->GetString(key);
420 if (!value.empty()) {
421 params[key] = value;
422 }
423 }
424 return params;
425 }
426
ParseExtraParams(const std::string & detectType,const std::unique_ptr<JsonValue> & item)427 std::map<std::string, std::string> WebDataDetectorAdapter::ParseExtraParams(
428 const std::string& detectType, const std::unique_ptr<JsonValue>& item)
429 {
430 if (!item || !item->IsObject()) {
431 return {};
432 }
433 auto type = ConvertTypeFromString(detectType);
434 if (type == TextDataDetectType::INVALID) {
435 return {};
436 }
437 std::map<std::string, std::string> params;
438 switch (type) {
439 case TextDataDetectType::DATE_TIME: {
440 auto startTimestamp = item->GetInt64("startTimestamp", -1);
441 if (startTimestamp != -1) {
442 params["startTimestamp"] = std::to_string(startTimestamp);
443 }
444 break;
445 }
446 default:
447 break;
448 }
449 return params;
450 }
451
MatchInOffsets(EntityMatch & match,const std::vector<std::pair<size_t,size_t>> & detectOffsets)452 int32_t WebDataDetectorAdapter::MatchInOffsets(
453 EntityMatch& match, const std::vector<std::pair<size_t, size_t>>& detectOffsets)
454 {
455 auto iter = std::upper_bound(
456 detectOffsets.begin(), detectOffsets.end(), std::make_pair(match.start, MAX_DETECT_LENGTH));
457 if (iter == detectOffsets.begin()) {
458 return -1;
459 }
460 --iter;
461 if (match.start >= iter->second || match.end <= iter->first || match.end > iter->second) {
462 return -1;
463 }
464 match.start -= iter->first;
465 match.end -= iter->first;
466 return std::distance(detectOffsets.begin(), iter);
467 }
468
GetResultJsonString(const std::string & requestId)469 std::string WebDataDetectorAdapter::GetResultJsonString(const std::string& requestId)
470 {
471 auto requestContext = GetRequestContext(requestId);
472 CHECK_NULL_RETURN(requestContext, "");
473 const auto& matches = requestContext->matches;
474
475 auto resultJson = JsonUtil::Create(true);
476 resultJson->Put("requestId", requestId.c_str());
477
478 auto styleJson = JsonUtil::Create(false);
479 styleJson->Put("color", config_.color.c_str());
480 styleJson->Put("textDecoration", config_.textDecorationStyle.c_str());
481 resultJson->PutRef("style", std::move(styleJson));
482
483 auto matchesJson = JsonUtil::CreateArray(false);
484 int32_t nodeIndex = 0;
485 for (const auto& nodeMatch : matches) {
486 auto nodeMatchJson = JsonUtil::Create(false);
487 nodeMatchJson->Put("nodeIndex", nodeIndex++);
488 auto matchArray = JsonUtil::CreateArray(false);
489 for (const auto& match : nodeMatch) {
490 auto matchJson = JsonUtil::Create(false);
491 matchJson->Put("start", match.start);
492 matchJson->Put("end", match.end);
493 matchJson->Put("hrefType", TEXT_DETECT_MAP_TO_HREF.at(match.entityType).c_str());
494 matchJson->Put("clean", match.clean.c_str());
495
496 auto attrsJson = JsonUtil::Create(false);
497 attrsJson->Put("OhosArkWebType", match.entityType.c_str());
498 for (auto [k, v] : match.params) {
499 attrsJson->Put(k.c_str(), v.c_str());
500 extraParamKeys_.insert(k);
501 }
502
503 matchJson->PutRef("attrs", std::move(attrsJson));
504 matchArray->PutRef(std::move(matchJson));
505 }
506 if (matchArray->GetArraySize() > 0) {
507 nodeMatchJson->PutRef("entities", std::move(matchArray));
508 matchesJson->PutRef(std::move(nodeMatchJson));
509 }
510 }
511 resultJson->PutRef("matches", std::move(matchesJson));
512 std::string resultStr = resultJson->ToString();
513 return resultStr;
514 }
515
ProcessClick(const std::string & jsonStr)516 void WebDataDetectorAdapter::ProcessClick(const std::string& jsonStr)
517 {
518 if (!GetDataDetectorEnable()) {
519 return;
520 }
521 auto instanceId = Container::CurrentIdSafelyWithCheck();
522 ContainerScope scope(instanceId);
523 auto msg = JsonUtil::ParseJsonString(jsonStr);
524 if (!msg || !msg->IsObject()) {
525 return;
526 }
527 auto content = msg->GetString("content");
528 auto outerHTML = msg->GetString("outerHTML");
529 auto entityType = msg->GetString("entityType");
530 if (auto touchTest = msg->GetBool("touchTest")) {
531 SetPreviewMenuAttr(ConvertTypeFromString(entityType), content, AttrsToParams(msg->GetObject("attrs")));
532 return;
533 }
534 SetPreviewMenuAttr();
535 auto rectJson = msg->GetObject("rect");
536 if (!rectJson || !rectJson->IsObject()) {
537 return;
538 }
539
540 auto left = rectJson->GetDouble("left");
541 auto top = rectJson->GetDouble("top");
542 auto right = rectJson->GetDouble("right");
543 auto bottom = rectJson->GetDouble("bottom");
544
545 TAG_LOGI(AceLogTag::ACE_WEB,
546 "WebDataDetectorAdapter::ProcessClick left= %{public}f, top= %{public}f, right= %{public}f, bottom= %{public}f",
547 left, top, right, bottom);
548
549 auto aiRect = CalcAIMenuRect(left, top, right, bottom);
550
551 AIMenuInfo info {
552 entityType,
553 content,
554 outerHTML,
555 aiRect,
556 };
557 AIPostTask(
558 [weak = AceType::WeakClaim(this), info, instanceId]() {
559 ContainerScope scope(instanceId);
560 auto adapter = weak.Upgrade();
561 CHECK_NULL_VOID(adapter);
562 if (adapter->ShowAIMenu(info)) {
563 adapter->CloseOtherMenu();
564 }
565 },
566 TaskExecutor::TaskType::UI, "ShowAIEntityMenuAsync");
567 }
568
InitAIMenu()569 void WebDataDetectorAdapter::InitAIMenu()
570 {
571 if (initAIMenu_) {
572 return;
573 }
574 GetAIMenu();
575 MenuParam menuParam;
576 menuParam.type = MenuType::CONTEXT_MENU;
577 menuParam.contextMenuRegisterType = ContextMenuRegisterType::CUSTOM_TYPE;
578 menuParam.previewMode = MenuPreviewMode::CUSTOM;
579 PaddingProperty paddings;
580 paddings.start = CalcLength(PREVIEW_MENU_MARGIN_LEFT);
581 paddings.end = CalcLength(PREVIEW_MENU_MARGIN_RIGHT);
582 menuParam.layoutRegionMargin = paddings;
583 menuParam.disappearScaleToTarget = true;
584 menuParam.isShow = true;
585
586 auto param = std::make_shared<WebPreviewSelectionMenuParam>(
587 WebElementType::AILINK, ResponseType::LONG_PRESS, nullptr, nullptr, menuParam);
588 auto pattern = DynamicCast<WebPattern>(pattern_.Upgrade());
589 CHECK_NULL_VOID(pattern);
590 pattern->SetPreviewSelectionMenu(param);
591 initAIMenu_ = true;
592 }
593
IsAISupported()594 bool WebDataDetectorAdapter::IsAISupported()
595 {
596 if (aiSupportStatus_ == AISupportStatus::UNKNOWN) {
597 aiSupportStatus_ = DataDetectorMgr::GetInstance().IsDataDetectorSupported() ? AISupportStatus::SUPPORTED
598 : AISupportStatus::UNSUPPORTED;
599 }
600 return aiSupportStatus_ == AISupportStatus::SUPPORTED;
601 }
602
GetAIMenu()603 void WebDataDetectorAdapter::GetAIMenu()
604 {
605 AIPostTask(
606 [weak = AceType::WeakClaim(this)] () {
607 auto adapter = weak.Upgrade();
608 CHECK_NULL_VOID(adapter);
609 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::GetAIMenu getting menu from ai_engine");
610 DataDetectorMgr::GetInstance().GetAIEntityMenu(adapter->textDetectResult_);
611 },
612 TaskExecutor::TaskType::UI, "GetAIMenu");
613 }
614
CalcAIMenuRect(double left,double top,double right,double bottom)615 RectF WebDataDetectorAdapter::CalcAIMenuRect(double left, double top, double right, double bottom)
616 {
617 auto pattern = DynamicCast<WebPattern>(pattern_.Upgrade());
618 CHECK_NULL_RETURN(pattern, RectF());
619 auto comX = pattern->GetHost()->GetTransformRelativeOffset().GetX();
620 auto comY = pattern->GetHost()->GetTransformRelativeOffset().GetY();
621 left = std::max(left + comX, 0.0);
622 top = std::max(top + comY, 0.0);
623 right = std::max(right + comX, 0.0);
624 bottom = std::max(bottom + comY, 0.0);
625 auto rect = RectF(left, top, right - left, bottom - top);
626 return rect;
627 }
628
GetAIMenuOptions(const AIMenuInfo & info,std::vector<std::pair<std::string,std::function<void ()>>> & menuOptions)629 bool WebDataDetectorAdapter::GetAIMenuOptions(
630 const AIMenuInfo& info, std::vector<std::pair<std::string, std::function<void()>>>& menuOptions)
631 {
632 if (textDetectResult_.menuOptionAndAction.empty()) {
633 GetAIMenu();
634 return false;
635 }
636 auto instanceId = Container::CurrentIdSafely();
637 menuOptions.clear();
638 auto menuOptionAndAction = textDetectResult_.menuOptionAndAction[info.entityType];
639 if (menuOptionAndAction.empty()) {
640 TAG_LOGE(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::GetAIMenuOptions menuOption empty");
641 return false;
642 }
643 TAG_LOGD(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::GetAIMenuOptions option size: %{public}zu",
644 menuOptionAndAction.size());
645
646 for (auto menuOption : menuOptionAndAction) {
647 menuOptions.emplace_back(
648 std::make_pair(menuOption.first, [weak = AceType::WeakClaim(this), info, menuOption, instanceId]() {
649 ContainerScope scope(instanceId);
650 auto adapter = weak.Upgrade();
651 CHECK_NULL_VOID(adapter);
652 adapter->OnClickAIMenuOption(info, menuOption);
653 }));
654 }
655 return true;
656 }
657
ShowAIMenu(const AIMenuInfo & info)658 bool WebDataDetectorAdapter::ShowAIMenu(const AIMenuInfo& info)
659 {
660 std::vector<std::pair<std::string, std::function<void()>>> menuOptions;
661 if (!GetAIMenuOptions(info, menuOptions)) {
662 return false;
663 }
664 auto pipeline = PipelineContext::GetCurrentContextSafely();
665 CHECK_NULL_RETURN(pipeline, false);
666 auto overlayManager = pipeline->GetOverlayManager();
667 CHECK_NULL_RETURN(overlayManager, false);
668 auto pattern = DynamicCast<WebPattern>(pattern_.Upgrade());
669 CHECK_NULL_RETURN(pattern, false);
670 auto targetNode = pattern->GetHost();
671 CHECK_NULL_RETURN(targetNode, false);
672 return overlayManager->ShowAIEntityMenu(menuOptions, info.rect, targetNode);
673 }
674
OnClickAIMenuOption(const AIMenuInfo & info,const std::pair<std::string,FuncVariant> & menuOption)675 void WebDataDetectorAdapter::OnClickAIMenuOption(
676 const AIMenuInfo& info, const std::pair<std::string, FuncVariant>& menuOption)
677 {
678 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::OnClickAIMenuOption click menuOption: %{public}s",
679 menuOption.first.c_str());
680 CloseAIMenu();
681
682 auto container = Container::Current();
683 CHECK_NULL_VOID(container);
684 auto ace_container = AceType::DynamicCast<Platform::AceContainer>(container);
685 CHECK_NULL_VOID(ace_container);
686 auto containerId = container->GetInstanceId();
687 auto token = ace_container->GetToken();
688 auto bundleName = container->GetBundleName();
689
690 TAG_LOGD(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::OnClickAIMenuOption prepare context");
691 auto visitor = [&](auto&& func) {
692 if (!func) {
693 return;
694 }
695 using T = std::decay_t<decltype(func)>;
696 if constexpr (std::is_same_v<T, std::function<std::string()>>) { // copy or selectText
697 auto action = func();
698 OnClickMenuItem(action, info);
699 } else if constexpr (std::is_same_v<T,
700 std::function<void(sptr<IRemoteObject>, std::string)>>) { // phone, email, url
701 func(token, info.content);
702 } else if constexpr (std::is_same_v<T, std::function<void(int32_t, std::string)>>) { // location
703 func(containerId, info.content);
704 } else if constexpr (std::is_same_v<T, std::function<void(int32_t, std::string, std::string, int32_t,
705 std::string)>>) { // datetime
706 func(containerId, info.content, bundleName, 0, info.content);
707 }
708 };
709 std::visit(visitor, menuOption.second);
710 }
711
OnClickMenuItem(const std::string & action,const AIMenuInfo & info)712 void WebDataDetectorAdapter::OnClickMenuItem(const std::string& action, const AIMenuInfo& info)
713 {
714 auto pattern = DynamicCast<WebPattern>(pattern_.Upgrade());
715 CHECK_NULL_VOID(pattern);
716 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::OnClickMenuItem action: %{public}s", action.c_str());
717 auto delegate = pattern->delegate_;
718 CHECK_NULL_VOID(delegate);
719 if (action == SELECT_TEXT) {
720 delegate->OnDataDetectorSelectText();
721 } else if (action == COPY) {
722 std::vector<std::string> recordMix { info.content, info.outerHTML };
723 delegate->OnDataDetectorCopy(recordMix);
724 }
725 }
726
OnClickAISelectMenuOption(TextDataDetectType type,const std::string & content)727 void WebDataDetectorAdapter::OnClickAISelectMenuOption(TextDataDetectType type, const std::string& content)
728 {
729 TAG_LOGI(
730 AceLogTag::ACE_WEB, "WebDataDetectorAdapter::OnClickAISelectMenuOption %{public}d", static_cast<int32_t>(type));
731 if (!GetDataDetectorEnable()) {
732 return;
733 }
734 if (textDetectResult_.menuOptionAndAction.empty()) {
735 GetAIMenu();
736 return;
737 }
738 int32_t index = static_cast<int32_t>(type);
739 if (index < 0 || index >= static_cast<int32_t>(TEXT_DETECT_LIST.size())) {
740 return;
741 }
742 auto entityType = TEXT_DETECT_LIST[index];
743 auto menuOptionAndAction = textDetectResult_.menuOptionAndAction[entityType];
744 if (menuOptionAndAction.empty()) {
745 TAG_LOGE(AceLogTag::ACE_WEB, "OnClickAISelectMenu menuOption empty");
746 return;
747 }
748 auto menuOption = menuOptionAndAction[0];
749 AIMenuInfo info;
750 info.entityType = entityType;
751 info.content = content;
752 OnClickAIMenuOption(info, menuOption);
753 }
754
DetectSelectedText(const std::string & detectText)755 void WebDataDetectorAdapter::DetectSelectedText(const std::string& detectText)
756 {
757 if (!GetDataDetectorEnable()) {
758 return;
759 }
760 TAG_LOGD(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::DetectSelectedText");
761 if (detectText.size() > MAX_SELECTED_TEXT_SIZE) {
762 return;
763 }
764
765 // ui thread
766 auto instanceId = Container::CurrentIdSafely();
767 auto resultFunc = [weak = AceType::WeakClaim(this), instanceId](const TextDataDetectResult result) {
768 // background thread
769 ContainerScope scope(instanceId);
770 auto adapter = weak.Upgrade();
771 CHECK_NULL_VOID(adapter);
772 adapter->OnDetectSelectedTextDone(result);
773 };
774 TextDataDetectInfo info { detectText, config_.types };
775 AIPostTask(
776 [info, resultFunc]() {
777 TAG_LOGI(AceLogTag::ACE_WEB,
778 "WebDataDetectorAdapter::DetectSelectedText start AI detect, length: %{public}zu", info.text.size());
779 DataDetectorMgr::GetInstance().DataDetect(info, resultFunc);
780 },
781 TaskExecutor::TaskType::BACKGROUND, "DataDetect");
782 }
783
ParseAIResultJson(std::unique_ptr<JsonValue> & entityJson)784 DataDetectorResult WebDataDetectorAdapter::ParseAIResultJson(std::unique_ptr<JsonValue>& entityJson)
785 {
786 DataDetectorResult result;
787 if (!entityJson || !entityJson->IsObject()) {
788 return result;
789 }
790 for (const auto& detectType : TEXT_DETECT_LIST) {
791 auto jsonValue = entityJson->GetValue(detectType);
792 if (!jsonValue || !jsonValue->IsArray()) {
793 continue;
794 }
795 int32_t arraySize = jsonValue->GetArraySize();
796 for (int32_t i = 0; i < arraySize; ++i) {
797 auto item = jsonValue->GetArrayItem(i);
798 if (!item || !item->IsObject()) {
799 continue;
800 }
801 auto charOffset = item->GetInt("charOffset");
802 if (charOffset < 0) {
803 TAG_LOGE(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::ParseAIResultJson charOffset invalid");
804 continue;
805 }
806 EntityMatch mat;
807 mat.start = static_cast<size_t>(charOffset); // u16 offset
808 mat.clean = item->GetString("oriText"); // u8 string
809 mat.end = mat.start + UtfUtils::Str8DebugToStr16(mat.clean).length(); // u16 offset not include
810 mat.entityType = detectType;
811 result.emplace_back(std::move(mat));
812 }
813 }
814 std::sort(
815 result.begin(), result.end(), [](const EntityMatch& a, const EntityMatch& b) { return a.start < b.start; });
816 return result;
817 }
818
OnDetectSelectedTextDone(const TextDataDetectResult & result)819 void WebDataDetectorAdapter::OnDetectSelectedTextDone(const TextDataDetectResult& result)
820 {
821 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::OnDetectSelectedTextDone receive AI result");
822 auto entityJson = JsonUtil::ParseJsonString(result.entity);
823 if (!entityJson || !entityJson->IsObject()) {
824 TAG_LOGE(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::OnDetectSelectedTextDone entityJson invalid");
825 return;
826 }
827 auto ret = ParseAIResultJson(entityJson);
828 if (ret.size() != 1) {
829 return;
830 }
831 AIPostTask(
832 [weak = AceType::WeakClaim(this), ret]() {
833 auto adapter = weak.Upgrade();
834 CHECK_NULL_VOID(adapter);
835 adapter->UpdateAISelectMenu(ret[0].entityType, ret[0].clean);
836 },
837 TaskExecutor::TaskType::UI, "UpdateAISelectMenu");
838 }
839
UpdateAISelectMenu(const std::string & entityType,const std::string & content)840 void WebDataDetectorAdapter::UpdateAISelectMenu(const std::string& entityType, const std::string& content)
841 {
842 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::UpdateAISelectMenu type: %{public}s", entityType.c_str());
843 TextDataDetectType type = ConvertTypeFromString(entityType);
844 if (type == TextDataDetectType::INVALID) {
845 return;
846 }
847 auto pattern = DynamicCast<WebPattern>(pattern_.Upgrade());
848 CHECK_NULL_VOID(pattern);
849 auto selectOverlay = pattern->webSelectOverlay_;
850 CHECK_NULL_VOID(selectOverlay);
851 if (selectOverlay->IsShowHandle()) {
852 selectOverlay->UpdateAISelectMenu(type, content);
853 }
854 }
855
UrlDecode(const std::string & str)856 std::string WebDataDetectorAdapter::UrlDecode(const std::string& str)
857 {
858 std::string decoded;
859 decoded.reserve(str.size());
860
861 for (size_t i = 0; i < str.size(); ++i) {
862 if (str[i] == '+') {
863 decoded += ' ';
864 } else if (str[i] == '%' && i + 2 < str.size()) {
865 if (std::isxdigit(str[i + 1]) && std::isxdigit(str[i + 2])) {
866 std::istringstream hexStream(str.substr(i + 1, 2));
867 int charCode;
868 hexStream >> std::hex >> charCode;
869
870 decoded += static_cast<char>(charCode);
871 i += 2;
872 } else {
873 decoded += str[i];
874 }
875 } else {
876 decoded += str[i];
877 }
878 }
879 return decoded;
880 }
881
ReplaceARGBToRGBA(const std::string & text)882 std::string WebDataDetectorAdapter::ReplaceARGBToRGBA(const std::string& text)
883 {
884 const std::regex argbRegex("#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})\\b");
885 std::string result = std::regex_replace(text, argbRegex, "#$2$3$4$1");
886 std::transform(result.begin(), result.end(), result.begin(), ::tolower);
887 return result;
888 }
889
SetPreviewMenuLink(const std::string & link)890 bool WebDataDetectorAdapter::SetPreviewMenuLink(const std::string& link)
891 {
892 if (!GetDataDetectorEnable()) {
893 return false;
894 }
895 auto linkType = TextDataDetectType::URL;
896 auto linkContent = link;
897 for (size_t i = 0; i < TEXT_DETECT_LIST.size(); ++i) {
898 if (static_cast<int32_t>(i) == static_cast<int32_t>(TextDataDetectType::URL)) {
899 continue;
900 }
901 auto entityType = TEXT_DETECT_LIST[i];
902 auto prefix = TEXT_DETECT_MAP_TO_HREF.at(entityType);
903 if (link.compare(0, prefix.length(), prefix) == 0) {
904 linkType = static_cast<TextDataDetectType>(i);
905 linkContent = link.substr(prefix.length());
906 break;
907 }
908 }
909 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::SetPreviewMenuLink type: %{public}d",
910 static_cast<int32_t>(linkType));
911 if (linkType == previewMenuType_) {
912 return true;
913 }
914 SetPreviewMenuAttr(linkType, UrlDecode(linkContent));
915 return true;
916 }
917
GetPreviewMenuBuilder(std::function<void ()> & menuBuilder,std::function<void ()> & previewBuilder)918 bool WebDataDetectorAdapter::GetPreviewMenuBuilder(
919 std::function<void()>& menuBuilder, std::function<void()>& previewBuilder)
920 {
921 if (!GetDataDetectorEnable() || previewMenuType_ == TextDataDetectType::INVALID) {
922 return false;
923 }
924 AIMenuInfo info;
925 info.entityType = TEXT_DETECT_LIST[static_cast<size_t>(previewMenuType_)];
926 info.content = previewMenuContent_;
927 info.outerHTML = GetLinkOuterHTML(info.entityType, info.content);
928 auto menuNode = GetPreviewMenuNode(info);
929 CHECK_NULL_RETURN(menuNode, false);
930 TAG_LOGI(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::GetPreviewMenuBuilder success");
931 menuBuilder = [menuNode, instanceId = Container::CurrentIdSafely()]() {
932 ContainerScope scope(instanceId);
933 std::optional<CalcLength> width = CalcLength(MENU_WIDTH);
934 auto layoutProperty = menuNode->GetLayoutProperty();
935 CHECK_NULL_VOID(layoutProperty);
936 layoutProperty->UpdateUserDefinedIdealSize(CalcSize(width, std::nullopt));
937 ViewStackProcessor::GetInstance()->Push(menuNode);
938 };
939 previewBuilder = [menuType = previewMenuType_, menuContent = previewMenuContent_,
940 menuExtraParams = previewMenuExtraParams_, instanceId = Container::CurrentIdSafely()]() {
941 ContainerScope scope(instanceId);
942 // arkui create preview menu static func
943 PreviewMenuController::CreatePreviewMenu(menuType, menuContent, nullptr);
944 };
945 SetPreviewMenuAttr();
946 return true;
947 }
948
GetLinkOuterHTML(const std::string & entityType,const std::string & content)949 std::string WebDataDetectorAdapter::GetLinkOuterHTML(const std::string& entityType, const std::string& content)
950 {
951 return R"(<a href=")" + entityType + ":" + content + R"(" style="color: )" + ReplaceARGBToRGBA(config_.color) +
952 R"(; text-decoration: )" + ReplaceARGBToRGBA(config_.textDecorationStyle) + R"(;">)" + content + R"(</a>)";
953 }
954
GetPreviewMenuOptionCallback(TextDataDetectType type,const std::string & content)955 std::function<void()> WebDataDetectorAdapter::GetPreviewMenuOptionCallback(
956 TextDataDetectType type, const std::string& content)
957 {
958 return [type, content, instanceId = Container::CurrentIdSafelyWithCheck()] () {
959 ContainerScope scope(instanceId);
960 auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck();
961 CHECK_NULL_VOID(pipeline);
962 auto fontManager = pipeline->GetFontManager();
963 CHECK_NULL_VOID(fontManager);
964 fontManager->OnPreviewMenuOptionClick(type, content);
965 };
966 }
GetPreviewMenuNode(const AIMenuInfo & info)967 RefPtr<FrameNode> WebDataDetectorAdapter::GetPreviewMenuNode(const AIMenuInfo& info)
968 {
969 std::vector<std::pair<std::string, std::function<void()>>> menuOptions;
970 if (!GetAIMenuOptions(info, menuOptions)) {
971 return nullptr;
972 }
973 auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck();
974 CHECK_NULL_RETURN(pipeline, nullptr);
975 auto overlayManager = pipeline->GetOverlayManager();
976 CHECK_NULL_RETURN(overlayManager, nullptr);
977
978 if (auto theme = pipeline->GetTheme<TextOverlayTheme>()) {
979 auto name = theme->GetAiMenuPreviewOptionName(previewMenuType_);
980 if (!menuOptions.empty() && !name.empty()) {
981 auto& option = menuOptions.front();
982 option.first = name;
983 option.second = GetPreviewMenuOptionCallback(previewMenuType_, previewMenuContent_);
984 }
985 }
986 return overlayManager->BuildAIEntityMenu(menuOptions);
987 }
988
SetPreviewMenuAttr(TextDataDetectType type,const std::string & content,const std::map<std::string,std::string> & params)989 void WebDataDetectorAdapter::SetPreviewMenuAttr(
990 TextDataDetectType type, const std::string& content, const std::map<std::string, std::string>& params)
991 {
992 previewMenuType_ = type;
993 previewMenuContent_ = (type != TextDataDetectType::INVALID) ? content : "";
994 previewMenuExtraParams_ = (type != TextDataDetectType::INVALID) ? params : std::map<std::string, std::string>();
995 }
996
ConvertTypeFromString(const std::string & type)997 TextDataDetectType WebDataDetectorAdapter::ConvertTypeFromString(const std::string& type)
998 {
999 auto it = std::find(TEXT_DETECT_LIST.begin(), TEXT_DETECT_LIST.end(), type);
1000 if (it == TEXT_DETECT_LIST.end()) {
1001 return TextDataDetectType::INVALID;
1002 }
1003 return static_cast<TextDataDetectType>(std::distance(TEXT_DETECT_LIST.begin(), it));
1004 }
1005
CloseAIMenu()1006 void WebDataDetectorAdapter::CloseAIMenu()
1007 {
1008 auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck();
1009 CHECK_NULL_VOID(pipeline);
1010 auto pattern = DynamicCast<WebPattern>(pattern_.Upgrade());
1011 CHECK_NULL_VOID(pattern);
1012 auto targetNode = pattern->GetHost();
1013 CHECK_NULL_VOID(targetNode);
1014 // Close Menu
1015 auto overlayManager = pipeline->GetOverlayManager();
1016 CHECK_NULL_VOID(overlayManager);
1017 overlayManager->CloseAIEntityMenu(targetNode->GetId());
1018 }
1019
CloseOtherMenu()1020 void WebDataDetectorAdapter::CloseOtherMenu()
1021 {
1022 auto pattern = DynamicCast<WebPattern>(pattern_.Upgrade());
1023 CHECK_NULL_VOID(pattern);
1024 pattern->CloseSelectOverlay();
1025 pattern->DestroyAnalyzerOverlay();
1026 auto delegate = pattern->delegate_;
1027 CHECK_NULL_VOID(delegate);
1028 delegate->OnContextMenuHide("");
1029 }
1030
AIPostTask(const std::function<void ()> & task,TaskExecutor::TaskType taskType,const std::string & name,uint32_t delay)1031 void WebDataDetectorAdapter::AIPostTask(
1032 const std::function<void()>& task, TaskExecutor::TaskType taskType, const std::string& name, uint32_t delay)
1033 {
1034 TAG_LOGD(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::AIPostTask start");
1035 auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck();
1036 CHECK_NULL_VOID(pipeline);
1037 auto taskExecutor = pipeline->GetTaskExecutor();
1038 CHECK_NULL_VOID(taskExecutor);
1039 bool result = taskExecutor->PostDelayedTask(task, taskType, delay, name);
1040 TAG_LOGD(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::AIPostTask end, result: %{public}d", result);
1041 }
1042
SetRequestContext(const std::string & requestId,DataDetectorRequestData && requestData)1043 bool WebDataDetectorAdapter::SetRequestContext(const std::string& requestId, DataDetectorRequestData&& requestData)
1044 {
1045 std::lock_guard<std::mutex> lock(contextMutex_);
1046 TAG_LOGD(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::SetRequestContext requestId: %{public}s", requestId.c_str());
1047 contextMap_[requestId] = std::make_shared<DataDetectorRequestData>(std::move(requestData));
1048 return true;
1049 }
1050
RemoveRequestContext(const std::string & requestId)1051 bool WebDataDetectorAdapter::RemoveRequestContext(const std::string& requestId)
1052 {
1053 std::lock_guard<std::mutex> lock(contextMutex_);
1054 TAG_LOGD(
1055 AceLogTag::ACE_WEB, "WebDataDetectorAdapter::RemoveRequestContext requestId: %{public}s", requestId.c_str());
1056 size_t count = contextMap_.erase(requestId);
1057 if (!count) {
1058 TAG_LOGE(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::RemoveRequestContext requestId: %{public}s not found",
1059 requestId.c_str());
1060 }
1061 return count > 0;
1062 }
1063
GetRequestContext(const std::string & requestId)1064 std::shared_ptr<DataDetectorRequestData> WebDataDetectorAdapter::GetRequestContext(const std::string& requestId)
1065 {
1066 std::lock_guard<std::mutex> lock(contextMutex_);
1067 auto it = contextMap_.find(requestId);
1068 if (it != contextMap_.end()) {
1069 TAG_LOGD(
1070 AceLogTag::ACE_WEB, "WebDataDetectorAdapter::GetRequestContext requestId: %{public}s", requestId.c_str());
1071 return it->second;
1072 }
1073 return nullptr;
1074 }
1075
ResetContextMap()1076 void WebDataDetectorAdapter::ResetContextMap()
1077 {
1078 std::lock_guard<std::mutex> lock(contextMutex_);
1079 TAG_LOGD(AceLogTag::ACE_WEB, "WebDataDetectorAdapter::ResetContextMap");
1080 contextMap_.clear();
1081 }
1082
1083 } // namespace OHOS::Ace::NG
1084