已合并
add book ai event report #77788
add book ai event report #77788
已合并
liuzihao创建于 2025年12月14日
共 13 个文件变更+1133-0
@@ -139,6 +139,15 @@ void FindListenerImpl::OnFindResultReceived(
139 delegate->OnSearchResultReceive(activeMatchOrdinal, numberOfMatches, isDoneCounting);139 delegate->OnSearchResultReceive(activeMatchOrdinal, numberOfMatches, isDoneCounting);
140}140}
141 141 
142+void WebAgentClientImpl::ReportEventJson(const std::string& json)
143+{
144+ TAG_LOGD(AceLogTag::ACE_WEB, "ReportEventJson: %{public}s", json.c_str());
145+ auto delegate = webDelegate_.Upgrade();
146+ CHECK_NULL_VOID(delegate);
147+ ContainerScope scope(delegate->GetInstanceId());
148+ delegate->ReportEventJson(json);
149+}
150+ 
142std::string SpanstringConvertHtmlImpl::SpanstringConvertHtml(const std::vector<uint8_t> &content)151std::string SpanstringConvertHtmlImpl::SpanstringConvertHtml(const std::vector<uint8_t> &content)
143{152{
144 ContainerScope scope(instanceId_);153 ContainerScope scope(instanceId_);
@@ -17,6 +17,7 @@
17#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_WEB_RESOURCE_WEBVIEW_CLIENT_IMPL_H17#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_WEB_RESOURCE_WEBVIEW_CLIENT_IMPL_H
18 18 
19#include "foundation/arkui/ace_engine/frameworks/base/memory/referenced.h"19#include "foundation/arkui/ace_engine/frameworks/base/memory/referenced.h"
20+#include "nweb_agent_handler.h"
20#include "nweb_drag_data.h"21#include "nweb_drag_data.h"
21#include "nweb_handler.h"22#include "nweb_handler.h"
22 23 
@@ -85,6 +86,23 @@ private:
85 WeakPtr<WebDelegate> webDelegate_;86 WeakPtr<WebDelegate> webDelegate_;
86};87};
87 88 
89+class WebAgentClientImpl : public std::enable_shared_from_this<WebAgentClientImpl>,
90+ public OHOS::NWeb::NWebAgentHandler {
91+public:
92+ WebAgentClientImpl() = default;
93+ ~WebAgentClientImpl() = default;
94+ 
95+ void ReportEventJson(const std::string& json) override;
96+ 
97+ void SetWebDelegate(const WeakPtr<WebDelegate>& delegate)
98+ {
99+ webDelegate_ = delegate;
100+ }
101+ 
102+private:
103+ WeakPtr<WebDelegate> webDelegate_;
104+};
105+ 
88class SpanstringConvertHtmlImpl : public OHOS::NWeb::NWebSpanstringConvertHtmlCallback {106class SpanstringConvertHtmlImpl : public OHOS::NWeb::NWebSpanstringConvertHtmlCallback {
89public:107public:
90 SpanstringConvertHtmlImpl() = default;108 SpanstringConvertHtmlImpl() = default;
@@ -36,6 +36,7 @@
36#include "base/utils/utils.h"36#include "base/utils/utils.h"
37#include "base/perfmonitor/perf_monitor.h"37#include "base/perfmonitor/perf_monitor.h"
38#include "core/accessibility/accessibility_manager.h"38#include "core/accessibility/accessibility_manager.h"
39+#include "core/components_ng/pattern/web/web_agent_event_reporter.h"
39#include "core/components_ng/render/detached_rs_node_manager.h"40#include "core/components_ng/render/detached_rs_node_manager.h"
40#include "core/components/container_modal/container_modal_constants.h"41#include "core/components/container_modal/container_modal_constants.h"
41#include "core/components/web/render_web.h"42#include "core/components/web/render_web.h"
@@ -3100,6 +3101,11 @@ void WebDelegate::InitWebViewWithWindow()
3100 findListenerImpl->SetWebDelegate(weak);3101 findListenerImpl->SetWebDelegate(weak);
3101 delegate->nweb_->PutFindCallback(findListenerImpl);3102 delegate->nweb_->PutFindCallback(findListenerImpl);
3102 3103 
3104+ // set agentClientImpl
3105+ auto agentClientImpl = std::make_shared<WebAgentClientImpl>();
3106+ agentClientImpl->SetWebDelegate(weak);
3107+ delegate->nweb_->SetNWebAgentHandler(agentClientImpl);
3108+ 
3103 auto spanstringConvertHtmlImpl = std::make_shared<SpanstringConvertHtmlImpl>(Container::CurrentId());3109 auto spanstringConvertHtmlImpl = std::make_shared<SpanstringConvertHtmlImpl>(Container::CurrentId());
3104 spanstringConvertHtmlImpl->SetWebDelegate(weak);3110 spanstringConvertHtmlImpl->SetWebDelegate(weak);
3105 delegate->nweb_->PutSpanstringConvertHtmlCallback(spanstringConvertHtmlImpl);3111 delegate->nweb_->PutSpanstringConvertHtmlCallback(spanstringConvertHtmlImpl);
@@ -3603,6 +3609,10 @@ void WebDelegate::InitWebViewWithSurface()
3603 downloadListenerImpl->SetWebDelegate(weak);3609 downloadListenerImpl->SetWebDelegate(weak);
3604 delegate->nweb_->SetNWebHandler(nweb_handler);3610 delegate->nweb_->SetNWebHandler(nweb_handler);
3605 delegate->nweb_->PutDownloadCallback(downloadListenerImpl);3611 delegate->nweb_->PutDownloadCallback(downloadListenerImpl);
3612+ // set aiClientImpl
3613+ auto agentClientImpl = std::make_shared<WebAgentClientImpl>();
3614+ agentClientImpl->SetWebDelegate(weak);
3615+ delegate->nweb_->SetNWebAgentHandler(agentClientImpl);
3606#ifdef OHOS_STANDARD_SYSTEM3616#ifdef OHOS_STANDARD_SYSTEM
3607 auto screenLockCallback = std::make_shared<NWebScreenLockCallbackImpl>(context);3617 auto screenLockCallback = std::make_shared<NWebScreenLockCallbackImpl>(context);
3608 delegate->nweb_->RegisterScreenLockFunction(Container::CurrentId(), screenLockCallback);3618 delegate->nweb_->RegisterScreenLockFunction(Container::CurrentId(), screenLockCallback);
@@ -4020,6 +4030,14 @@ std::shared_ptr<OHOS::NWeb::NWeb> WebDelegate::GetNweb()
4020 return nweb_;4030 return nweb_;
4021}4031}
4022 4032 
4033+std::shared_ptr<OHOS::NWeb::NWebAgentManager> WebDelegate::GetNWebAgentManager()
4034+{
4035+ if (!nweb_) {
liuzihao
liuzihaoliuzihao2025年12月18日

CHECK_NULL_RETURN

likedislike
4036+ return nullptr;
4037+ }
4038+ return nweb_->GetAgentManager();
4039+}
4040+ 
4023bool WebDelegate::GetForceDarkMode()4041bool WebDelegate::GetForceDarkMode()
4024{4042{
4025 return forceDarkMode_;4043 return forceDarkMode_;
@@ -8800,6 +8818,15 @@ std::vector<int8_t> WebDelegate::GetWordSelection(const std::string& text, int8_
8800 return webPattern->GetWordSelection(text, offset);8818 return webPattern->GetWordSelection(text, offset);
8801}8819}
8802 8820 
8821+void WebDelegate::ReportEventJson(const std::string& jsonString)
8822+{
8823+ auto webPattern = webPattern_.Upgrade();
8824+ CHECK_NULL_VOID(webPattern);
8825+ auto reporter = webPattern->GetAgentEventReporter();
8826+ CHECK_NULL_VOID(reporter);
8827+ reporter->AddEvent(jsonString);
8828+}
8829+ 
8803void WebDelegate::NotifyForNextTouchEvent()8830void WebDelegate::NotifyForNextTouchEvent()
8804{8831{
8805 ACE_DCHECK(nweb_ != nullptr);8832 ACE_DCHECK(nweb_ != nullptr);
@@ -9557,6 +9584,7 @@ void WebDelegate::OnTextSelectionChange(const std::string& selectionText, bool i
9557 CHECK_NULL_VOID(delegate);9584 CHECK_NULL_VOID(delegate);
9558 auto webPattern = delegate->webPattern_.Upgrade();9585 auto webPattern = delegate->webPattern_.Upgrade();
9559 CHECK_NULL_VOID(webPattern);9586 CHECK_NULL_VOID(webPattern);
9587+ webPattern->ReportSelectedText();
9560 auto webEventHub = webPattern->GetWebEventHub();9588 auto webEventHub = webPattern->GetWebEventHub();
9561 CHECK_NULL_VOID(webEventHub);9589 CHECK_NULL_VOID(webEventHub);
9562 webEventHub->FireOnTextSelectionChangeEvent(std::make_shared<TextSelectionChangedEvent>(selectionText));9590 webEventHub->FireOnTextSelectionChangeEvent(std::make_shared<TextSelectionChangedEvent>(selectionText));
@@ -1024,6 +1024,7 @@ public:
1024 void UpdateGeolocationEnabled(const bool& isGeolocationAccessEnabled);1024 void UpdateGeolocationEnabled(const bool& isGeolocationAccessEnabled);
1025 void UpdateCacheMode(const WebCacheMode& mode);1025 void UpdateCacheMode(const WebCacheMode& mode);
1026 std::shared_ptr<OHOS::NWeb::NWeb> GetNweb();1026 std::shared_ptr<OHOS::NWeb::NWeb> GetNweb();
1027+ std::shared_ptr<OHOS::NWeb::NWebAgentManager> GetNWebAgentManager();
1027 bool GetForceDarkMode();1028 bool GetForceDarkMode();
1028 void OnConfigurationUpdated(const OHOS::AppExecFwk::Configuration& configuration);1029 void OnConfigurationUpdated(const OHOS::AppExecFwk::Configuration& configuration);
1029 void UpdateDarkMode(const WebDarkMode& mode);1030 void UpdateDarkMode(const WebDarkMode& mode);
@@ -1375,6 +1376,8 @@ public:
1375 int GetSelectStartIndex() const;1376 int GetSelectStartIndex() const;
1376 int GetSelectEndIndex() const;1377 int GetSelectEndIndex() const;
1377 1378 
1379+ void ReportEventJson(const std::string& jsonString);
1380+ 
1378 void OnViewportFitChange(OHOS::NWeb::ViewportFit viewportFit);1381 void OnViewportFitChange(OHOS::NWeb::ViewportFit viewportFit);
1379 void OnCameraCaptureStateChanged(int originalState, int newState);1382 void OnCameraCaptureStateChanged(int originalState, int newState);
1380 void OnMicrophoneCaptureStateChanged(int originalState, int newState);1383 void OnMicrophoneCaptureStateChanged(int originalState, int newState);
@@ -34,6 +34,7 @@ build_component_ng("web_pattern_ng") {
34 "transitional_node_info.cpp",34 "transitional_node_info.cpp",
35 "web_accessibility_child_tree_callback.cpp",35 "web_accessibility_child_tree_callback.cpp",
36 "web_accessibility_event_report.cpp",36 "web_accessibility_event_report.cpp",
37+ "web_agent_event_reporter.cpp",
37 "web_accessibility_property.cpp",38 "web_accessibility_property.cpp",
38 "web_accessibility_session_adapter.cpp",39 "web_accessibility_session_adapter.cpp",
39 "web_content_modifier.cpp",40 "web_content_modifier.cpp",
@@ -0,0 +1,315 @@
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_agent_event_reporter.h"
17+ 
18+#include "interfaces/inner_api/ui_session/ui_session_manager.h"
19+ 
20+#include "core/components_ng/pattern/web/web_pattern.h"
21+ 
22+namespace OHOS::Ace::NG {
23+ 
24+WebAgentEventReporter::WebAgentEventReporter(const WeakPtr<Pattern>& pattern)
25+{
26+ pattern_ = pattern;
27+}
28+ 
29+void WebAgentEventReporter::AddEvent(const std::string& jsonString)
arkui-feature-manager
arkui-feature-managerarkui-feature-manager2025年12月14日

🤖 AI 代码检视意见(直接回复本评论可解决检视意见)

🟠 JSON输入验证不足

位置: L29-L58 | 严重程度: High


❓ 问题描述

AddEvent函数直接解析外部JSON输入,没有对JSON字符串长度和内容进行适当验证。恶意构造的超大JSON字符串可能导致内存耗尽或拒绝服务攻击。函数中仅检查了JSON格式有效性,但没有验证关键字段的存在和类型。

💡 修复建议

添加输入验证和大小限制:

void WebAgentEventReporter::AddEvent(const std::string& jsonString)
{
    // 限制JSON大小(例如1MB)
    if (jsonString.size() > MAX_JSON_SIZE) {
        TAG_LOGE(AceLogTag::ACE_WEB, "JSON too large: %{public}zu", jsonString.size());
        return;
    }
    
    auto jsonValue = JsonUtil::ParseJsonString(jsonString);
    if (!jsonValue || !jsonValue->IsObject()) {
        TAG_LOGE(AceLogTag::ACE_WEB, "Invalid JSON format");
        return;
    }
    
    // 验证必需字段
    auto eventType = jsonValue->GetString("EventType");
    if (eventType.empty()) {
        TAG_LOGE(AceLogTag::ACE_WEB, "Missing EventType field");
        return;
    }
    
    // 继续处理...
}

likedislike
liuzihao
liuzihao
2025年12月17日 评论:
30+{
31+ auto jsonValue = JsonUtil::ParseJsonString(jsonString);
32+ if (!jsonValue || !jsonValue->IsObject()) {
33+ TAG_LOGE(AceLogTag::ACE_WEB, "WebAgentEventReporter::AddEvent parse json failed");
34+ return;
35+ }
36+ auto eventType = jsonValue->GetString(EVENT_IDENTIFIER);
37+ TAG_LOGD(AceLogTag::ACE_WEB, "WebAgentEventReporter::AddEvent |%{public}s|", eventType.c_str());
38+ auto type = ParseEventType(eventType);
39+ switch (type) {
40+ case WebEventType::Tap:
41+ AddTapEvent(jsonValue);
42+ break;
43+ case WebEventType::LongPress:
44+ AddLongPressEvent(jsonValue);
45+ break;
46+ case WebEventType::ScrollStart:
47+ case WebEventType::ScrollEnd:
48+ AddScrollEvent(jsonValue);
49+ break;
50+ case WebEventType::PinchEnd:
51+ AddPinchEvent(jsonValue);
52+ break;
53+ default:
54+ TAG_LOGE(AceLogTag::ACE_WEB, "WebAgentEventReporter::AddEvent unknown event type: |%{public}s|",
55+ eventType.c_str());
56+ break;
57+ }
58+}
59+ 
60+void WebAgentEventReporter::AddTapEvent(const std::unique_ptr<JsonValue>& tapEventJson)
61+{
62+ auto jsonValue = InspectorJsonUtil::Create();
63+ jsonValue->Put(REPORT_EVENT_IDENTIFIER, "Tap");
64+ jsonValue->Put("id", tapEventJson->GetInt("id"));
65+ RectF pointRect;
66+ auto pointJson = tapEventJson->GetValue("point");
arkui-feature-manager
arkui-feature-managerarkui-feature-manager2025年12月14日

🤖 AI 代码检视意见(直接回复本评论可解决检视意见)

🟡 数组访问缺少边界检查

位置: L66-L73 | 严重程度: Medium


❓ 问题描述

AddTapEvent函数中直接访问数组元素而没有充分验证数组大小,虽然检查了size==2,但没有验证数组元素的有效性,可能导致无效数据被处理。

💡 修复建议

增强验证逻辑:

void WebAgentEventReporter::AddTapEvent(const std::unique_ptr<JsonValue>& tapEventJson)
{
    auto pointJson = tapEventJson->GetValue("point");
    if (!pointJson || !pointJson->IsArray()) {
        TAG_LOGE(AceLogTag::ACE_WEB, "Invalid point structure");
        return;
    }
    
    if (pointJson->GetArraySize() != 2) {
        TAG_LOGE(AceLogTag::ACE_WEB, "Invalid point array size");
        return;
    }
    
    auto xItem = pointJson->GetArrayItem(0);
    auto yItem = pointJson->GetArrayItem(1);
    if (!xItem || !yItem || !xItem->IsNumber() || !yItem->IsNumber()) {
        TAG_LOGE(AceLogTag::ACE_WEB, "Invalid point values");
        return;
    }
    
    pointRect.SetLeft(xItem->GetDouble());
    pointRect.SetTop(yItem->GetDouble());
    // ...
}

likedislike
liuzihao
liuzihao
2025年12月17日 评论:
67+ if (pointJson && pointJson->IsArray() && pointJson->GetArraySize() == 2) {
68+ pointRect.SetLeft(pointJson->GetArrayItem(0)->GetDouble());
69+ pointRect.SetTop(pointJson->GetArrayItem(1)->GetDouble());
70+ } else {
71+ TAG_LOGE(AceLogTag::ACE_WEB, "WebAgentEventReporter::AddTapEvent invalid point json");
72+ return;
73+ }
74+ pointRect = ConvertToWindow(pointRect);
75+ auto pointArray = InspectorJsonUtil::CreateArray();
76+ pointArray->Put(pointRect.Left());
77+ pointArray->Put(pointRect.Top());
78+ jsonValue->Put("point", pointArray);
79+ jsonValue->Put("count", tapEventJson->GetInt("count"));
80+ ReportEventImediately(jsonValue);
81+}
82+ 
83+void WebAgentEventReporter::AddLongPressEvent(const std::unique_ptr<JsonValue>& longPressEventJson)
84+{
85+ auto jsonValue = InspectorJsonUtil::Create();
86+ jsonValue->Put(REPORT_EVENT_IDENTIFIER, "LongPress");
87+ jsonValue->Put("id", longPressEventJson->GetInt("id"));
88+ RectF pointRect;
89+ auto pointJson = longPressEventJson->GetValue("point");
90+ if (pointJson && pointJson->IsArray() && pointJson->GetArraySize() == 2) {
91+ pointRect.SetLeft(pointJson->GetArrayItem(0)->GetDouble());
92+ pointRect.SetTop(pointJson->GetArrayItem(1)->GetDouble());
93+ } else {
94+ TAG_LOGE(AceLogTag::ACE_WEB, "WebAgentEventReporter::AddLongPressEvent invalid point json");
95+ return;
96+ }
97+ pointRect = ConvertToWindow(pointRect);
98+ auto pointArray = InspectorJsonUtil::CreateArray();
99+ pointArray->Put(pointRect.Left());
100+ pointArray->Put(pointRect.Top());
101+ jsonValue->Put("point", pointArray);
102+ 
103+ int64_t duration = LONG_PRESS_TIMEOUT_MS;
104+ if (auto pattern = DynamicCast<WebPattern>(pattern_.Upgrade())) {
105+ duration = GetCurrentTimestamp() - pattern->lastTouchDownTime_;
106+ duration = std::min(duration, static_cast<int64_t>(std::numeric_limits<int32_t>::max()));
107+ }
108+ jsonValue->Put("actualDuration", static_cast<int32_t>(duration));
109+ 
110+ ReportEventImediately(jsonValue);
111+}
112+ 
113+void WebAgentEventReporter::AddPinchEvent(const std::unique_ptr<JsonValue>& pinchEventJson)
114+{
115+ auto jsonValue = InspectorJsonUtil::Create();
116+ jsonValue->Put(REPORT_EVENT_IDENTIFIER, pinchEventJson->GetString(EVENT_IDENTIFIER).c_str());
117+ jsonValue->Put("id", pinchEventJson->GetInt("id"));
liuzihao
liuzihaoliuzihao2025年12月18日

不需要id

likedislike
118+ double scale = pinchEventJson->GetDouble("scale");
119+ jsonValue->Put("scale", scale);
120+ ReportEventImediately(jsonValue);
121+}
122+ 
123+WebAgentEventReporter::WebEventType WebAgentEventReporter::ParseEventType(const std::string& str)
124+{
liuzihao
liuzihaoliuzihao2025年12月18日

建议map

likedislike
125+ if (str == "Tap")
126+ return WebEventType::Tap;
127+ if (str == "LongPress")
128+ return WebEventType::LongPress;
129+ if (str == "ScrollStart")
130+ return WebEventType::ScrollStart;
131+ if (str == "ScrollEnd")
132+ return WebEventType::ScrollEnd;
133+ if (str == "PinchEnd")
134+ return WebEventType::PinchEnd;
135+ return WebEventType::Unknown;
136+}
137+ 
138+RectF WebAgentEventReporter::ConvertToWindow(const RectF& rect, bool addComponentOffset)
liuzihao
liuzihaoliuzihao2025年12月18日

拆成两个原子函数

likedislike
139+{
140+ auto pattern = DynamicCast<WebPattern>(pattern_.Upgrade());
141+ CHECK_NULL_RETURN(pattern, rect);
142+ auto host = pattern->GetHost();
143+ CHECK_NULL_RETURN(host, rect);
144+ double dsf = pattern->density_;
145+ double comX = addComponentOffset ? host->GetTransformRelativeOffset().GetX() : 0.0;
146+ double comY = addComponentOffset ? host->GetTransformRelativeOffset().GetY() : 0.0;
147+ return RectF(rect.Left() * dsf + comX, rect.Top() * dsf + comY, rect.Width() * dsf, rect.Height() * dsf);
148+}
149+ 
150+// ===== scroll event process begin =====
151+void WebAgentEventReporter::AddScrollEvent(const std::unique_ptr<JsonValue>& scrollEventJson)
152+{
153+ ScrollEventInfo info;
154+ info.eventType = scrollEventJson->GetString(EVENT_IDENTIFIER);
155+ info.id = scrollEventJson->GetInt("id");
156+ double scrollLeft = 0.0;
157+ double scrollTop = 0.0;
158+ auto offsetJson = scrollEventJson->GetValue("offset");
159+ if (offsetJson && offsetJson->IsArray() && offsetJson->GetArraySize() == 2) {
160+ scrollLeft = offsetJson->GetArrayItem(0)->GetDouble();
161+ scrollTop = offsetJson->GetArrayItem(1)->GetDouble();
162+ } else {
163+ TAG_LOGE(AceLogTag::ACE_WEB, "WebAgentEventReporter::AddScrollEvent invalid offset json");
164+ return;
165+ }
166+ info.scrollRect = ConvertToWindow(RectF(scrollLeft, scrollTop, 0.0, 0.0), false);
167+ // no lock here, called in UI thread
168+ scrollQueue_.push(info);
arkui-feature-manager
arkui-feature-managerarkui-feature-manager2025年12月14日

🤖 AI 代码检视意见(直接回复本评论可解决检视意见)

🔴 队列操作无锁保护

位置: L160-L162 | 严重程度: Critical


❓ 问题描述

scrollQueue_队列在没有同步保护的情况下被访问和修改。虽然注释说'called in UI thread',但在多线程环境中这是危险的,可能导致数据竞争、队列状态损坏或崩溃。

💡 修复建议

添加互斥锁保护:

class WebAgentEventReporter : public AceType {
private:
    std::mutex scrollQueueMutex_;
    std::queue<ScrollEventInfo> scrollQueue_;
    // ...
};

void WebAgentEventReporter::AddScrollEvent(const std::unique_ptr<JsonValue>& scrollEventJson)
{
    ScrollEventInfo info;
    // ... 填充info ...
    
    {
        std::lock_guard<std::mutex> lock(scrollQueueMutex_);
        scrollQueue_.push(info);
    }
    ProcessScrollQueue();
}

likedislike
liuzihao
liuzihao
2025年12月17日 评论:
169+ ProcessScrollQueue();
170+}
171+ 
172+std::shared_ptr<InspectorJsonValue> WebAgentEventReporter::ScrollEventInfo::GetInspectorJsonValue() const
173+{
174+ auto jsonValue = InspectorJsonUtil::Create();
175+ jsonValue->Put(REPORT_EVENT_IDENTIFIER, eventType.c_str());
176+ jsonValue->Put("id", id);
177+ 
178+ auto offsetArray = InspectorJsonUtil::CreateArray();
179+ offsetArray->Put(scrollRect.Left());
180+ offsetArray->Put(scrollRect.Top());
181+ 
182+ jsonValue->Put("offset", offsetArray);
183+ return jsonValue;
184+}
185+ 
186+bool WebAgentEventReporter::UpdateScrollEventInfoFromTree(ScrollEventInfo& info)
187+{
188+ auto pattern = DynamicCast<WebPattern>(pattern_.Upgrade());
189+ CHECK_NULL_RETURN(pattern, false);
190+ auto [id, rect] = pattern->GetScrollAreaInfoFromDocument(info.id);
191+ if (id > 0) {
192+ info.id = id;
193+ info.scrollRect = rect; // related to window
194+ return true;
195+ }
196+ TAG_LOGE(AceLogTag::ACE_WEB, "UpdateScrollEventInfoFromTree failed");
197+ return false;
198+}
199+ 
200+void WebAgentEventReporter::ContinueScrollProcessing()
201+{
202+ processingScroll_ = false;
203+ ProcessScrollQueue();
204+}
205+ 
206+// Handle an updated end scroll event. Returns true if processing should return early
207+// (either scheduled a retry or dropped the end event because no matching start record).
208+WebAgentEventReporter::ScrollEndEventAction WebAgentEventReporter::HandleUpdatedScrollEndEvent(ScrollEventInfo& info)
209+{
210+ auto it = startRecords_.find(info.id);
211+ if (it != startRecords_.end()) {
212+ bool same = NearEqual(it->second.scrollRect.Left(), info.scrollRect.Left()) &&
213+ NearEqual(it->second.scrollRect.Top(), info.scrollRect.Top());
214+ if (same && info.retry < MAX_SCROLL_EVENT_UPDATE_RETRY) {
arkui-feature-manager
arkui-feature-managerarkui-feature-manager2025年12月14日

🤖 AI 代码检视意见(直接回复本评论可解决检视意见)

🟢 重试机制缺乏退避策略

位置: L200-L213 | 严重程度: Low


❓ 问题描述

滚动事件的重试机制使用固定延迟(16ms),没有指数退避或最大重试次数限制,可能在持续失败的情况下导致资源浪费。

💡 修复建议

改进重试策略:

// 在HandleUpdatedScrollEndEvent中
if (same && info.retry < MAX_SCROLL_EVENT_UPDATE_RETRY) {
    ++info.retry;
    // 使用指数退避
    uint32_t delay = DELAYED_SCROLL_EVENT_PROCESS_MS * (1 << std::min(info.retry, 5));
    TAG_LOGI(AceLogTag::ACE_WEB, "Scheduling retry %{public}d with delay %{public}ums", 
             info.retry, delay);
    AIPostTask(
        [weak = AceType::WeakClaim(this), retryCount = info.retry]() {
            auto reporter = weak.Upgrade();
            CHECK_NULL_VOID(reporter);
            reporter->ContinueScrollProcessing();
        },
        TaskExecutor::TaskType::UI, "ContinueScrollProcessing", delay);
    return ScrollEndEventAction::RETRY;
}

likedislike
liuzihao
liuzihao
2025年12月17日 评论:
215+ ++info.retry;
216+ TAG_LOGI(AceLogTag::ACE_WEB,
217+ "WebAgentEventReporter::HandleUpdatedScrollEndEvent scroll offset not changed, retry: %{public}d",
218+ info.retry);
219+ AIPostTask(
220+ [weak = AceType::WeakClaim(this)]() {
221+ auto reporter = weak.Upgrade();
222+ CHECK_NULL_VOID(reporter);
223+ reporter->ContinueScrollProcessing();
224+ },
225+ TaskExecutor::TaskType::UI, "ContinueScrollProcessing", DELAYED_SCROLL_EVENT_PROCESS_MS);
liuzihao
liuzihaoliuzihao2025年12月18日

动态变化

likedislike
226+ return ScrollEndEventAction::RETRY; // scheduled retry, caller should return
227+ }
228+ return ScrollEndEventAction::PROCEED; // proceed to report
229+ } else {
230+ TAG_LOGE(AceLogTag::ACE_WEB, "WebAgentEventReporter::HandleUpdatedScrollEndEvent no match start record found");
231+ // drop this end event and let caller pop/continue
232+ return ScrollEndEventAction::DROP;
233+ }
234+}
235+ 
236+// Report the scroll event and advance the queue
237+void WebAgentEventReporter::ReportAndAdvanceScrollQueue(const ScrollEventInfo& info, bool isScrollStart)
238+{
239+ TAG_LOGI(AceLogTag::ACE_WEB, "WebAgentEventReporter::ReportAndAdvanceScrollQueue, isScrollStart: %{public}d",
240+ isScrollStart);
241+ if (isScrollStart) {
242+ startRecords_[info.id] = info;
243+ } else {
244+ startRecords_.erase(info.id);
245+ }
246+ auto jsonValue = info.GetInspectorJsonValue();
247+ scrollQueue_.pop();
248+ ReportEventImediately(jsonValue);
249+}
250+ 
251+void WebAgentEventReporter::ProcessScrollQueue()
252+{
arkui-feature-manager
arkui-feature-managerarkui-feature-manager2025年12月14日

🤖 AI 代码检视意见(直接回复本评论可解决检视意见)

🟠 处理标志非原子访问

位置: L236-L274 | 严重程度: High


❓ 问题描述

processingScroll_布尔标志在没有原子操作保护的情况下被检查和修改。在并发环境中可能导致竞态条件,使得多个线程同时处理队列。

💡 修复建议

使用原子操作:

class WebAgentEventReporter : public AceType {
private:
    std::atomic<bool> processingScroll_{false};
    // ...
};

void WebAgentEventReporter::ProcessScrollQueue()
{
    bool expected = false;
    if (!processingScroll_.compare_exchange_strong(expected, true)) {
        TAG_LOGD(AceLogTag::ACE_WEB, "Already processing");
        return;
    }
    
    // 处理队列...
    
    processingScroll_ = false;
}

likedislike
liuzihao
liuzihao
2025年12月17日 评论:
253+ if (processingScroll_) {
254+ TAG_LOGI(AceLogTag::ACE_WEB, "WebAgentEventReporter::ProcessScrollQueue already processing");
255+ return;
256+ }
257+ if (scrollQueue_.empty()) {
258+ TAG_LOGE(AceLogTag::ACE_WEB, "WebAgentEventReporter::ProcessScrollQueue empty");
259+ return;
260+ }
261+ 
262+ processingScroll_ = true;
263+ // Process items iteratively to avoid recursion.
264+ while (!scrollQueue_.empty()) {
265+ ScrollEventInfo& info = scrollQueue_.front(); // copy so we can pop safely
266+ bool isScrollStart = (info.eventType == "ScrollStart");
267+ bool updated = UpdateScrollEventInfoFromTree(info);
268+ if (updated) {
269+ TAG_LOGI(AceLogTag::ACE_WEB, "WebAgentEventReporter::ProcessScrollQueue updated from tree");
270+ if (!isScrollStart) {
271+ auto action = HandleUpdatedScrollEndEvent(info);
272+ if (action == ScrollEndEventAction::RETRY) {
273+ // retry scheduled; keep processingScroll_ true to match previous behavior
274+ return;
275+ } else if (action == ScrollEndEventAction::DROP) {
276+ // drop this event and continue with next
277+ scrollQueue_.pop();
278+ continue;
279+ }
280+ // else PROCEED -> fall through to report
281+ }
282+ }
283+ 
284+ // report event immediately and continue loop
285+ ReportAndAdvanceScrollQueue(info, isScrollStart);
286+ }
287+ 
288+ // processed all queued items
289+ processingScroll_ = false;
290+}
291+// ===== scroll event process end =====
292+ 
293+void WebAgentEventReporter::ReportEventImediately(const std::shared_ptr<InspectorJsonValue>& eventJson)
294+{
295+ TAG_LOGD(AceLogTag::ACE_WEB, "WebAgentEventReporter::ReportEventImediately");
296+ auto pattern = DynamicCast<WebPattern>(pattern_.Upgrade());
297+ CHECK_NULL_VOID(pattern);
298+ auto host = pattern->GetHost();
299+ CHECK_NULL_VOID(host);
300+ UiSessionManager::GetInstance()->ReportComponentChangeEvent(host->GetId(), "event", eventJson);
301+}
302+ 
303+void WebAgentEventReporter::AIPostTask(
304+ const std::function<void()>& task, TaskExecutor::TaskType taskType, const std::string& name, uint32_t delay)
305+{
306+ TAG_LOGD(AceLogTag::ACE_WEB, "WebAgentEventReporter::AIPostTask start");
307+ auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck();
308+ CHECK_NULL_VOID(pipeline);
309+ auto taskExecutor = pipeline->GetTaskExecutor();
310+ CHECK_NULL_VOID(taskExecutor);
311+ bool result = taskExecutor->PostDelayedTask(task, taskType, delay, name);
312+ TAG_LOGD(AceLogTag::ACE_WEB, "WebAgentEventReporter::AIPostTask end, result: %{public}d", result);
313+}
314+ 
315+} // namespace OHOS::Ace::NG
@@ -0,0 +1,101 @@
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+#ifndef FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_WEB_WEB_AGENT_EVENT_REPORT_H
17+#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_WEB_WEB_AGENT_EVENT_REPORT_H
18+ 
19+#include <functional>
20+#include <string>
21+ 
22+#include "base/memory/ace_type.h"
23+#include "base/memory/referenced.h"
24+#include "core/components/web/web_property.h"
25+ 
26+namespace OHOS::Ace {
27+class InspectorJsonValue;
28+}
29+ 
30+namespace OHOS::Ace::NG {
31+class Pattern;
32+ 
33+class WebAgentEventReporter : public AceType {
34+ DECLARE_ACE_TYPE(WebAgentEventReporter, AceType);
35+ 
36+public:
37+ explicit WebAgentEventReporter(const WeakPtr<Pattern>& pattern);
38+ 
39+ void AddEvent(const std::string& jsonString);
40+ 
41+ virtual void ReportEventImediately(const std::shared_ptr<InspectorJsonValue>& eventJson);
liuzihao
liuzihaoliuzihao2025年12月18日

拼写错误ReportEventImediately

likedislike
42+ 
43+ virtual void AddTapEvent(const std::unique_ptr<JsonValue>& tapEventJson);
44+ 
45+ virtual void AddLongPressEvent(const std::unique_ptr<JsonValue>& longPressEventJson);
46+ 
47+ virtual void AddPinchEvent(const std::unique_ptr<JsonValue>& pinchEventJson);
48+ 
49+ virtual void AddScrollEvent(const std::unique_ptr<JsonValue>& scrollEventJson);
50+ 
51+ void AIPostTask(const std::function<void()>& task, TaskExecutor::TaskType taskType, const std::string& name,
52+ uint32_t delay = 0);
53+ 
54+ enum class WebEventType { Unknown, Tap, LongPress, ScrollStart, ScrollEnd, PinchEnd };
55+ 
56+ WebEventType ParseEventType(const std::string& str);
57+ 
58+ constexpr static char EVENT_IDENTIFIER[] = "EventType";
59+ 
60+ constexpr static char REPORT_EVENT_IDENTIFIER[] = "WebEventType";
61+ 
62+ constexpr static int32_t LONG_PRESS_TIMEOUT_MS = 550;
63+ 
64+private:
65+ WeakPtr<Pattern> pattern_;
66+ 
67+protected:
68+ virtual RectF ConvertToWindow(const RectF& rect, bool addComponentOffset = true);
69+ 
70+ // ===== scroll event process start =====
71+ struct ScrollEventInfo {
72+ std::string eventType;
73+ int32_t id = 0;
74+ RectF scrollRect;
75+ int retry = 0;
76+ 
77+ std::shared_ptr<InspectorJsonValue> GetInspectorJsonValue() const;
78+ };
79+ std::queue<ScrollEventInfo> scrollQueue_;
arkui-feature-manager
arkui-feature-managerarkui-feature-manager2025年12月14日

🤖 AI 代码检视意见(直接回复本评论可解决检视意见)

🟡 队列无大小限制

位置: L76-L81 | 严重程度: Medium


❓ 问题描述

scrollQueue_队列没有大小限制,在快速滚动或事件爆发的情况下可能导致无限增长,消耗大量内存。

💡 修复建议

添加队列大小限制:

class WebAgentEventReporter : public AceType {
private:
    static constexpr size_t MAX_QUEUE_SIZE = 100;
    std::queue<ScrollEventInfo> scrollQueue_;
    // ...
};

// 在AddScrollEvent中
{
    std::lock_guard<std::mutex> lock(scrollQueueMutex_);
    if (scrollQueue_.size() >= MAX_QUEUE_SIZE) {
        TAG_LOGW(AceLogTag::ACE_WEB, "Scroll queue full, dropping event");
        return;
    }
    scrollQueue_.push(info);
}

likedislike
liuzihao
liuzihao
2025年12月17日 评论:
80+ std::unordered_map<int32_t, ScrollEventInfo> startRecords_;
81+ bool processingScroll_ = false;
82+ 
83+ static constexpr int32_t DELAYED_SCROLL_EVENT_PROCESS_MS = 16;
84+ static constexpr int32_t MAX_SCROLL_EVENT_UPDATE_RETRY = 2;
85+ 
86+ virtual void ProcessScrollQueue();
87+ 
88+ void ContinueScrollProcessing();
89+ 
90+ virtual bool UpdateScrollEventInfoFromTree(ScrollEventInfo& info);
91+ enum class ScrollEndEventAction { RETRY, DROP, PROCEED };
92+ // Handle updated end event and return an action for the caller to take.
93+ virtual ScrollEndEventAction HandleUpdatedScrollEndEvent(ScrollEventInfo& info);
94+ // Report the scroll event and advance the queue (does not schedule further processing).
95+ void ReportAndAdvanceScrollQueue(const ScrollEventInfo& info, bool isScrollStart);
96+ // ===== scroll event process end =====
97+};
98+ 
99+} // namespace OHOS::Ace::NG
100+ 
101+#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_WEB_WEB_AGENT_EVENT_REPORT_H
@@ -4902,6 +4902,7 @@ void WebPattern::HandleTouchDown(const TouchEventInfo& info, bool fromOverlay)
4902 if (!ParseTouchInfo(info, touchInfos)) {4902 if (!ParseTouchInfo(info, touchInfos)) {
4903 return;4903 return;
4904 }4904 }
4905+ lastTouchDownTime_ = GetCurrentTimestamp();
4905 for (auto& touchPoint : touchInfos) {4906 for (auto& touchPoint : touchInfos) {
4906 if (fromOverlay) {4907 if (fromOverlay) {
4907 touchPoint.x -= webOffset_.GetX();4908 touchPoint.x -= webOffset_.GetX();
@@ -9391,6 +9392,32 @@ bool WebPattern::IsAccessibilityUsedByEventReport()
9391 return false;9392 return false;
9392}9393}
9393 9394 
9395+ 
9396+RefPtr<WebAgentEventReporter> WebPattern::GetAgentEventReporter()
9397+{
9398+ if (!webAgentEventReporter_) {
9399+ TAG_LOGI(AceLogTag::ACE_WEB, "WebPattern::GetAgentEventReporter, create new agent report instance");
9400+ webAgentEventReporter_ = AceType::MakeRefPtr<WebAgentEventReporter>(WeakClaim(this));
9401+ }
9402+ return webAgentEventReporter_;
9403+}
9404+ 
9405+void WebPattern::ReportSelectedText()
9406+{
9407+ if (UiSessionManager::GetInstance()->GetSelectTextEventRegistered()) {
liuzihao
liuzihaoliuzihao2025年12月18日

arkui注册时是否有调用ReportSelectedText

likedislike
9408+ CHECK_NULL_VOID(delegate_);
9409+ auto text = delegate_->GetLastSelectionText();
9410+ TAG_LOGI(AceLogTag::ACE_WEB, "WebPattern::ReportSelectedText %{public}zu", text.size());
9411+ UiSessionManager::GetInstance()->ReportSelectTextEvent(text);
9412+ }
9413+}
9414+ 
9415+std::pair<int32_t, RectF> WebPattern::GetScrollAreaInfoFromDocument(int32_t id)
9416+{
9417+ CHECK_NULL_RETURN(webDomDocument_, std::make_pair(-1, RectF()));
9418+ return webDomDocument_->GetScrollAreaInfoById(id);
9419+}
9420+ 
9394RefPtr<WebDataDetectorAdapter> WebPattern::GetDataDetectorAdapter()9421RefPtr<WebDataDetectorAdapter> WebPattern::GetDataDetectorAdapter()
9395{9422{
9396 if (!webDataDetectorAdapter_) {9423 if (!webDataDetectorAdapter_) {
@@ -57,6 +57,7 @@
57#include "core/components_ng/gestures/pinch_gesture.h"57#include "core/components_ng/gestures/pinch_gesture.h"
58#include "core/components_ng/pattern/select_overlay/magnifier.h"58#include "core/components_ng/pattern/select_overlay/magnifier.h"
59#include "core/components_ng/pattern/select_overlay/magnifier_controller.h"59#include "core/components_ng/pattern/select_overlay/magnifier_controller.h"
60+#include "core/components_ng/pattern/web/web_agent_event_reporter.h"
60#include "core/components_ng/pattern/web/web_data_detector_adapter.h"61#include "core/components_ng/pattern/web/web_data_detector_adapter.h"
61#include "ui/rs_surface_node.h"62#include "ui/rs_surface_node.h"
62#include "core/components_ng/pattern/web/web_select_overlay.h"63#include "core/components_ng/pattern/web/web_select_overlay.h"
@@ -91,6 +92,7 @@ namespace OHOS::NWeb {
91 enum class CursorType;92 enum class CursorType;
92}93}
93namespace OHOS::Ace::NG {94namespace OHOS::Ace::NG {
95+class WebAgentEventReporter;
94class WebAccessibilityChildTreeCallback;96class WebAccessibilityChildTreeCallback;
95class ViewDataCommon;97class ViewDataCommon;
96class TransitionalNodeInfo;98class TransitionalNodeInfo;
@@ -973,6 +975,12 @@ public:
973 void SetTextEventAccessibilityEnable(bool enable);975 void SetTextEventAccessibilityEnable(bool enable);
974 bool IsAccessibilityUsedByEventReport();976 bool IsAccessibilityUsedByEventReport();
975 977 
978+ // WebAgentEventReporter funcs
979+ RefPtr<WebAgentEventReporter> GetAgentEventReporter();
980+ // WebAgentEventReporter reference
981+ void ReportSelectedText() override;
982+ std::pair<int32_t, RectF> GetScrollAreaInfoFromDocument(int32_t id);
983+ 
976 // Data Detector funcs984 // Data Detector funcs
977 RefPtr<WebDataDetectorAdapter> GetDataDetectorAdapter();985 RefPtr<WebDataDetectorAdapter> GetDataDetectorAdapter();
978 986 
@@ -1035,6 +1043,7 @@ private:
1035 friend class WebSelectOverlay;1043 friend class WebSelectOverlay;
1036 friend class WebDataDetectorAdapter;1044 friend class WebDataDetectorAdapter;
1037 friend class WebAccessibilityEventReport;1045 friend class WebAccessibilityEventReport;
1046+ friend class WebAgentEventReporter;
1038 1047 
1039 void FakePageNodeInfo();1048 void FakePageNodeInfo();
1040 bool Pip(int status, int delegateId, int childId, int frameRoutingId, int width, int height);1049 bool Pip(int status, int delegateId, int childId, int frameRoutingId, int width, int height);
@@ -1575,6 +1584,9 @@ private:
1575 1584 
1576 // properties for WebAccessibilityEventReport1585 // properties for WebAccessibilityEventReport
1577 RefPtr<WebAccessibilityEventReport> webAccessibilityEventReport_ = nullptr;1586 RefPtr<WebAccessibilityEventReport> webAccessibilityEventReport_ = nullptr;
1587+ // properties for WebAgentEventReporter
1588+ RefPtr<WebAgentEventReporter> webAgentEventReporter_ = nullptr;
1589+ int64_t lastTouchDownTime_ = 0;
1578 1590 
1579 // properties for AI data detector1591 // properties for AI data detector
1580 bool isAILinkMenuShow_ = false;1592 bool isAILinkMenuShow_ = false;
@@ -284,6 +284,7 @@ ohos_unittest("web_pattern_unit_test_ohos_part_one") {
284 "$ace_root/frameworks/core/components_ng/pattern/web/web_accessibility_child_tree_callback.cpp",284 "$ace_root/frameworks/core/components_ng/pattern/web/web_accessibility_child_tree_callback.cpp",
285 "$ace_root/frameworks/core/components_ng/pattern/web/web_accessibility_property.cpp",285 "$ace_root/frameworks/core/components_ng/pattern/web/web_accessibility_property.cpp",
286 "$ace_root/frameworks/core/components_ng/pattern/web/web_accessibility_session_adapter.cpp",286 "$ace_root/frameworks/core/components_ng/pattern/web/web_accessibility_session_adapter.cpp",
287+ "$ace_root/frameworks/core/components_ng/pattern/web/web_agent_event_reporter.cpp",
287 "$ace_root/frameworks/core/components_ng/pattern/web/web_content_modifier.cpp",288 "$ace_root/frameworks/core/components_ng/pattern/web/web_content_modifier.cpp",
288 "$ace_root/frameworks/core/components_ng/pattern/web/web_context_select_overlay.cpp",289 "$ace_root/frameworks/core/components_ng/pattern/web/web_context_select_overlay.cpp",
289 "$ace_root/frameworks/core/components_ng/pattern/web/web_data_detector_adapter.cpp",290 "$ace_root/frameworks/core/components_ng/pattern/web/web_data_detector_adapter.cpp",
@@ -297,6 +298,7 @@ ohos_unittest("web_pattern_unit_test_ohos_part_one") {
297 "mock_rosen_render_surface.cpp",298 "mock_rosen_render_surface.cpp",
298 "mock_web_delegate.cpp",299 "mock_web_delegate.cpp",
299 "web_pattern_test_ng_part_one.cpp",300 "web_pattern_test_ng_part_one.cpp",
301+ "web_agent_event_reporter_test.cpp"
300 ]302 ]
301 303 
302 deps = [304 deps = [
@@ -28,6 +28,7 @@ const std::string STATUS_TRUE = "true";
28static std::string g_setComponentType = "";28static std::string g_setComponentType = "";
29const std::string STATUS_FALSE = "false";29const std::string STATUS_FALSE = "false";
30std::shared_ptr<NWeb::NWebAccessibilityNodeInfo> g_customAccessibilityNode = nullptr;30std::shared_ptr<NWeb::NWebAccessibilityNodeInfo> g_customAccessibilityNode = nullptr;
31+std::shared_ptr<NWeb::NWebAgentManager> g_nwebAgentManager = nullptr;
31std::map<std::string, std::string> htmlElementToSurfaceMap = { { "existhtmlElementId", "existSurfaceId" },32std::map<std::string, std::string> htmlElementToSurfaceMap = { { "existhtmlElementId", "existSurfaceId" },
32 { "existhtmlElementIdOther", "existSurfaceIdOther" } };33 { "existhtmlElementIdOther", "existSurfaceIdOther" } };
33std::map<std::string, std::string> surfaceToHtmlElementMap = { { "existSurfaceId", "existhtmlElementId" },34std::map<std::string, std::string> surfaceToHtmlElementMap = { { "existSurfaceId", "existhtmlElementId" },
@@ -1354,6 +1355,12 @@ std::string WebDelegate::GetWebInfoType()
1354}1355}
1355void WebDelegate::SetSurfaceId(const std::string& surfaceId) {}1356void WebDelegate::SetSurfaceId(const std::string& surfaceId) {}
1356void WebDelegate::OnAdsBlocked(const std::string& url, const std::vector<std::string>& adsBlocked) {}1357void WebDelegate::OnAdsBlocked(const std::string& url, const std::vector<std::string>& adsBlocked) {}
1358+ 
1359+std::shared_ptr<OHOS::NWeb::NWebAgentManager> WebDelegate::GetNWebAgentManager()
1360+{
1361+ return g_nwebAgentManager;
1362+}
1363+ 
1357std::string WebDelegate::SpanstringConvertHtml(const std::vector<uint8_t>& content)1364std::string WebDelegate::SpanstringConvertHtml(const std::vector<uint8_t>& content)
1358{1365{
1359 return "";1366 return "";
@@ -21,6 +21,7 @@
21 21 
22namespace OHOS::NWeb {22namespace OHOS::NWeb {
23class NWebAccessibilityNodeInfo;23class NWebAccessibilityNodeInfo;
24+class NWebAgentManager;
24}25}
25namespace OHOS::Ace {26namespace OHOS::Ace {
26void SetReturnStatus(const std::string& status);27void SetReturnStatus(const std::string& status);
@@ -0,0 +1,609 @@
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 "gtest/gtest.h"
17+ 
18+#define private public
19+#define protected public
20+#include "test/mock/base/mock_task_executor.h"
21+#include "test/mock/core/common/mock_container.h"
22+#include "test/mock/core/pipeline/mock_pipeline_context.h"
23+#include "test/unittest/core/pattern/web/mock_web_delegate.h"
24+ 
25+#include "adapter/ohos/entrance/ace_container.h"
26+#include "core/components/web/resource/web_delegate.h"
27+#include "core/components/web/web_property.h"
28+#include "core/components_ng/pattern/web/web_pattern.h"
29+#include "frameworks/core/pipeline/pipeline_base.h"
30+#undef protected
31+#undef private
32+ 
33+#include "interfaces/inner_api/ui_session/ui_session_manager.h"
34+ 
35+#include "core/components_ng/base/view_stack_processor.h"
36+ 
37+using namespace testing;
38+using namespace testing::ext;
39+ 
40+namespace OHOS::Ace::NG {
41+namespace {
42+RefPtr<WebPattern> g_webPattern = nullptr;
43+ 
44+class MockReporterForSpecificEvent : public WebAgentEventReporter {
45+public:
46+ using WebAgentEventReporter::WebAgentEventReporter;
47+ MOCK_METHOD(RectF, ConvertToWindow, (const RectF& rect, bool addComponentOffset), (override));
48+ MOCK_METHOD(void, ReportEventImediately, (const std::shared_ptr<InspectorJsonValue>& eventJson), (override));
49+ MOCK_METHOD(void, ProcessScrollQueue, (), (override));
50+};
51+ 
52+class MockReporterForAddEvent : public WebAgentEventReporter {
53+public:
54+ using WebAgentEventReporter::WebAgentEventReporter;
55+ MOCK_METHOD(void, AddTapEvent, (const std::unique_ptr<JsonValue>& tapEventJson), (override));
56+ MOCK_METHOD(void, AddLongPressEvent, (const std::unique_ptr<JsonValue>& longPressEventJson), (override));
57+ MOCK_METHOD(void, AddPinchEvent, (const std::unique_ptr<JsonValue>& pinchEventJson), (override));
58+ MOCK_METHOD(void, AddScrollEvent, (const std::unique_ptr<JsonValue>& scrollEventJson), (override));
59+};
60+ 
61+// A helper mock that overrides Update and Handle so the real ProcessScrollQueue
62+// implementation can be tested while controlling Update/Handle outcomes.
63+class MockReporterUpdateHandle : public WebAgentEventReporter {
64+public:
65+ using WebAgentEventReporter::WebAgentEventReporter;
66+ MOCK_METHOD(bool, UpdateScrollEventInfoFromTree, (ScrollEventInfo & info), (override));
67+ MOCK_METHOD(ScrollEndEventAction, HandleUpdatedScrollEndEvent, (ScrollEventInfo & info), (override));
68+};
69+} // namespace
70+ 
71+class WebAgentEventReporterTest : public testing::Test {
72+public:
73+ static void SetUpTestCase();
74+ static void TearDownTestCase();
75+ void SetUp() override;
76+ void TearDown() override;
77+};
78+ 
79+void WebAgentEventReporterTest::SetUpTestCase()
80+{
81+#ifdef OHOS_STANDARD_SYSTEM
82+ MockPipelineContext::SetUp();
83+ MockContainer::SetUp();
84+ MockContainer::Current()->taskExecutor_ = AceType::MakeRefPtr<MockTaskExecutor>();
85+ MockContainer::Current()->pipelineContext_ = MockPipelineContext::GetCurrentContext();
86+ MockContainer::Current()->pipelineContext_->taskExecutor_ = MockContainer::Current()->taskExecutor_;
87+ 
88+ std::string src = "web_test";
89+ RefPtr<WebController> controller = AceType::MakeRefPtr<WebController>();
90+ auto* stack = ViewStackProcessor::GetInstance();
91+ auto nodeId = stack->ClaimNodeId();
92+ auto frameNode = FrameNode::GetOrCreateFrameNode(
93+ V2::WEB_ETS_TAG, nodeId, [src, controller]() { return AceType::MakeRefPtr<WebPattern>(src, controller); });
94+ stack->Push(frameNode);
95+ 
96+ g_webPattern = frameNode->GetPattern<WebPattern>();
97+ CHECK_NULL_VOID(g_webPattern);
98+ g_webPattern->OnModifyDone();
99+#endif
100+}