• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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