From 8766a0d0b7738f60d14f7a69eebb9bbf9689a344 Mon Sep 17 00:00:00 2001 From: arnonchen <745735906@qq.com> Date: Tue, 12 Nov 2024 21:13:21 +0800 Subject: [PATCH] fix(devtools): fix the mapping between DOM tree and UI inspector --- .../src/module/domain/dom_domain.cc | 4 +- .../native/include/devtools/devtools_utils.h | 7 +- .../native/src/devtools_utils.cc | 92 ++++++++++++------- .../tencent/mtt/hippy/utils/DevtoolsUtil.java | 54 +++++++++++ .../component/view/HippyViewManager.mm | 39 +++++++- 5 files changed, 154 insertions(+), 42 deletions(-) diff --git a/devtools/devtools-backend/src/module/domain/dom_domain.cc b/devtools/devtools-backend/src/module/domain/dom_domain.cc index fd553690f52..37c7176c20e 100644 --- a/devtools/devtools-backend/src/module/domain/dom_domain.cc +++ b/devtools/devtools-backend/src/module/domain/dom_domain.cc @@ -172,8 +172,8 @@ void DomDomain::GetBoxModel(const DomNodeDataRequest& request) { } void DomDomain::GetNodeForLocation(const DomNodeForLocationRequest& request) { - if (!dom_data_call_back_) { - ResponseErrorToFrontend(request.GetId(), kErrorFailCode, "GetNodeForLocation, dom_data_callback is null"); + if (!location_for_node_call_back_) { + ResponseErrorToFrontend(request.GetId(), kErrorFailCode, "GetNodeForLocation, location_for_node_call_back is null"); return; } if (!request.HasSetXY()) { diff --git a/devtools/devtools-integration/native/include/devtools/devtools_utils.h b/devtools/devtools-integration/native/include/devtools/devtools_utils.h index cce16310167..19a7ded9426 100644 --- a/devtools/devtools-integration/native/include/devtools/devtools_utils.h +++ b/devtools/devtools-integration/native/include/devtools/devtools_utils.h @@ -25,6 +25,7 @@ #include "api/adapter/data/domain_metas.h" #include "dom/dom_manager.h" #include "dom/dom_node.h" +#include "dom/root_node.h" namespace hippy::devtools { /** @@ -45,7 +46,7 @@ class DevToolsUtil { uint32_t depth, const std::shared_ptr& dom_manager); - static DomNodeLocation GetNodeIdByDomLocation(const std::shared_ptr& root_node, double x, double y); + static DomNodeLocation GetNodeIdByDomLocation(const std::shared_ptr& root_node, double x, double y); static DomPushNodePathMetas GetPushNodeByPath(const std::shared_ptr& dom_node, std::vector> path); @@ -55,8 +56,8 @@ class DevToolsUtil { static bool ShouldAvoidPostDomManagerTask(const std::string& event_name); private: - static std::shared_ptr GetHitNode(const std::shared_ptr& root_node, const std::shared_ptr& node, double x, double y); - static bool IsLocationHitNode(const std::shared_ptr& root_node, const std::shared_ptr& dom_node, double x, double y); + static hippy::dom::DomArgument makeLocationArgument(double x, double y); + static std::shared_ptr GetHitNode(const std::shared_ptr& root_node, double x, double y); static std::string ParseNodeKeyProps(const std::string& node_key, const NodePropsUnorderedMap& node_props); static std::string ParseNodeProps(const NodePropsUnorderedMap& node_props); static std::string ParseNodeProps(const std::unordered_map& node_props); diff --git a/devtools/devtools-integration/native/src/devtools_utils.cc b/devtools/devtools-integration/native/src/devtools_utils.cc index 8a49d3ff605..d2aa412543b 100644 --- a/devtools/devtools-integration/native/src/devtools_utils.cc +++ b/devtools/devtools-integration/native/src/devtools_utils.cc @@ -26,6 +26,8 @@ constexpr char kDefaultNodeName[] = "DefaultNode"; constexpr char kAttributes[] = "attributes"; constexpr char kText[] = "text"; constexpr char kGetLocationOnScreen[] = "getLocationOnScreen"; +constexpr char kGetViewTagByLocation[] = "getViewTagByLocation"; +constexpr char kHippyTag[] = "hippyTag"; constexpr char kXOnScreen[] = "xOnScreen"; constexpr char kYOnScreen[] = "yOnScreen"; constexpr char kViewWidth[] = "viewWidth"; @@ -91,8 +93,8 @@ DomainMetas DevToolsUtil::GetDomDomainData(const std::shared_ptr& root_ return metas; } -DomNodeLocation DevToolsUtil::GetNodeIdByDomLocation(const std::shared_ptr& root_node, double x, double y) { - auto hit_node = GetHitNode(root_node, root_node, x, y); +DomNodeLocation DevToolsUtil::GetNodeIdByDomLocation(const std::shared_ptr& root_node, double x, double y) { + auto hit_node = GetHitNode(root_node, x, y); FOOTSTONE_LOG(INFO) << "GetNodeIdByDomLocation hit_node:" << hit_node << ", " << x << ",y:" << y; if (hit_node == nullptr) { hit_node = root_node; @@ -127,36 +129,6 @@ DomPushNodePathMetas DevToolsUtil::GetPushNodeByPath(const std::shared_ptr DevToolsUtil::GetHitNode(const std::shared_ptr& root_node, const std::shared_ptr& node, double x, double y) { - if (node == nullptr || !IsLocationHitNode(root_node, node, x, y)) { - return nullptr; - } - std::shared_ptr hit_node = node; - for (auto& child : node->GetChildren()) { - if (!IsLocationHitNode(root_node, child, x, y)) { - continue; - } - auto new_node = GetHitNode(root_node, child, x, y); - if (hit_node == nullptr) { - hit_node = new_node; - } else if (new_node != nullptr) { - auto hit_node_area = hit_node->GetLayoutNode()->GetWidth() * hit_node->GetLayoutNode()->GetHeight(); - auto new_node_area = new_node->GetLayoutNode()->GetWidth() * new_node->GetLayoutNode()->GetHeight(); - hit_node = hit_node_area > new_node_area ? new_node : hit_node; - } - } - return hit_node; -} - -bool DevToolsUtil::IsLocationHitNode(const std::shared_ptr& root_node, const std::shared_ptr& dom_node, double x, double y) { - LayoutResult layout_result = GetLayoutOnScreen(root_node, dom_node); - double self_x = static_cast(layout_result.left); - double self_y = static_cast(layout_result.top); - bool in_top_offset = (x >= self_x) && (y >= self_y); - bool in_bottom_offset = (x <= self_x + layout_result.width) && (y <= self_y + layout_result.height); - return in_top_offset && in_bottom_offset; -} - template auto MakeCopyable(F&& f) { auto s = std::make_shared>(std::forward(f)); @@ -165,6 +137,60 @@ auto MakeCopyable(F&& f) { }; } +hippy::dom::DomArgument DevToolsUtil::makeLocationArgument(double x, double y) { + footstone::value::HippyValue::HippyValueObjectType hippy_value_object; + hippy_value_object[kXOnScreen] = footstone::value::HippyValue(x); + hippy_value_object[kYOnScreen] = footstone::value::HippyValue(y); + footstone::value::HippyValue::HippyValueArrayType hippy_value_array; + hippy_value_array.push_back(footstone::value::HippyValue(hippy_value_object)); + footstone::value::HippyValue argument_hippy_value(hippy_value_array); + hippy::dom::DomArgument argument(argument_hippy_value); + return argument; +} + +std::shared_ptr DevToolsUtil::GetHitNode(const std::shared_ptr& root_node, double x, double y) { + std::shared_ptr base_node = nullptr; + auto children = root_node->GetChildren(); + if (!children.empty()) { + base_node = children[0]; + } + std::promise layout_promise; + std::future read_file_future = layout_promise.get_future(); + if (!base_node) { + return root_node; + } + + hippy::dom::DomArgument argument = makeLocationArgument(x, y); + auto screen_shot_callback = + MakeCopyable([promise = std::move(layout_promise)](std::shared_ptr arg) mutable { + footstone::value::HippyValue result_hippy_value; + arg->ToObject(result_hippy_value); + footstone::value::HippyValue::HippyValueObjectType result_dom_object; + if (result_hippy_value.IsArray() && !result_hippy_value.ToArrayChecked().empty()) { + result_dom_object = result_hippy_value.ToArrayChecked()[0].ToObjectChecked(); + } else if (result_hippy_value.IsObject()) { + result_dom_object = result_hippy_value.ToObjectChecked(); + } else { + promise.set_value(-1); + return; + } + int hippyTag = static_cast(result_dom_object.find(kHippyTag)->second.ToInt32Checked()); + promise.set_value(hippyTag); + }); + base_node->CallFunction(kGetViewTagByLocation, argument, screen_shot_callback); + std::chrono::milliseconds span(10); + if (read_file_future.wait_for(span) == std::future_status::timeout) { + FOOTSTONE_DLOG(WARNING) << kDevToolsTag << "GetHitNode wait_for timeout"; + return base_node; + } + std::shared_ptr targetNode = base_node; + int hippyTag = read_file_future.get(); + if (hippyTag > 0) { + targetNode = root_node->GetNode(static_cast(hippyTag)); + } + return targetNode; +} + LayoutResult DevToolsUtil::GetLayoutOnScreen(const std::shared_ptr& root_node, const std::shared_ptr& dom_node) { std::shared_ptr find_node = nullptr; if (dom_node == root_node) { @@ -419,7 +445,7 @@ void DevToolsUtil::PostDomTask(const std::weak_ptr& weak_dom_manager * callback must not be posted in the same task runner. */ bool DevToolsUtil::ShouldAvoidPostDomManagerTask(const std::string& event_name) { - return event_name == kGetLocationOnScreen; + return event_name == kGetLocationOnScreen || event_name == kGetViewTagByLocation; } } // namespace hippy::devtools diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/utils/DevtoolsUtil.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/utils/DevtoolsUtil.java index 2a32858bfac..0edf61622e6 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/utils/DevtoolsUtil.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/utils/DevtoolsUtil.java @@ -32,6 +32,7 @@ import android.view.PixelCopy; import android.view.PixelCopy.OnPixelCopyFinishedListener; import android.view.View; +import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnDrawListener; import android.view.Window; @@ -59,12 +60,14 @@ public class DevtoolsUtil { public static final String ADD_FRAME_CALLBACK = "addFrameCallback"; public static final String REMOVE_FRAME_CALLBACK = "removeFrameCallback"; public static final String GET_LOCATION_IN_SCREEN = "getLocationOnScreen"; + public static final String GET_VIEW_TAG_BY_LOCATION = "getViewTagByLocation"; private static final String TAG = "tdf_DevtoolsUtil"; private static final String SCREEN_SHOT = "screenShot"; private static final String SCREEN_WIDTH = "width"; private static final String SCREEN_HEIGHT = "height"; private static final String VIEW_WIDTH = "viewWidth"; private static final String VIEW_HEIGHT = "viewHeight"; + private static final String HIPPY_TAG = "hippyTag"; private static final String SCREEN_SCALE = "screenScale"; private static final String FRAME_CALLBACK_ID = "frameCallbackId"; private static final String X_ON_SCREEN = "xOnScreen"; @@ -96,6 +99,9 @@ public static void dispatchDevtoolsFunction(@NonNull View view, @NonNull String case DevtoolsUtil.GET_LOCATION_IN_SCREEN: DevtoolsUtil.getLocationOnScreen(view, promise); break; + case DevtoolsUtil.GET_VIEW_TAG_BY_LOCATION: + DevtoolsUtil.getViewTagByLocation(params, view, promise); + break; default: break; } @@ -120,6 +126,54 @@ private static void getLocationOnScreen(@NonNull View view, @NonNull Promise pro promise.resolve(resultMap); } + private static View findSmallestViewContainingPoint(@NonNull View rootView, int x, int y) { + Rect rect = new Rect(); + rootView.getGlobalVisibleRect(rect); + if (!rect.contains(x, y)) { + return null; + } + View targetView = rootView; + if (rootView instanceof ViewGroup) { + ViewGroup rootViewGroup = (ViewGroup) rootView; + for (int i = 0; i < rootViewGroup.getChildCount(); i++) { + View child = rootViewGroup.getChildAt(i); + View subview = findSmallestViewContainingPoint(child, x, y); + if (subview != null) { + int subArea = subview.getWidth() * subview.getHeight(); + int targetArea = targetView.getWidth() * targetView.getHeight(); + if (subArea < targetArea) { + targetView = subview; + } + } + } + } + return targetView; + } + + private static void getViewTagByLocation(@NonNull List params, @NonNull View view, @NonNull Promise promise) { + HippyMap resultMap = new HippyMap(); + resultMap.pushInt(HIPPY_TAG, -1); + NativeRender renderer = NativeRendererManager.getNativeRenderer(view.getContext()); + View rootView = renderer == null ? null : renderer.getRootView(view); + if (params.isEmpty() || rootView == null) { + promise.resolve(resultMap); + return; + } + Map param = ArrayUtils.getMapValue(params, 0); + if (param != null) { + int xOnScreen = (int) MapUtils.getDoubleValue(param, X_ON_SCREEN, 0); + int yOnScreen = (int) MapUtils.getDoubleValue(param, Y_ON_SCREEN, 0); + int[] rootLocation = new int[2]; + rootView.getLocationOnScreen(rootLocation); + View targetView = findSmallestViewContainingPoint(rootView, xOnScreen + rootLocation[0], + yOnScreen + rootLocation[1]); + if (targetView != null) { + resultMap.pushInt(HIPPY_TAG, targetView.getId()); + } + } + promise.resolve(resultMap); + } + public static void addFrameCallback(@NonNull List params, @NonNull View view, @NonNull final Promise promise) { NativeRender nativeRenderer = NativeRendererManager.getNativeRenderer(view.getContext()); if (nativeRenderer == null) { diff --git a/renderer/native/ios/renderer/component/view/HippyViewManager.mm b/renderer/native/ios/renderer/component/view/HippyViewManager.mm index 05cbaa8948e..b6e363fb909 100644 --- a/renderer/native/ios/renderer/component/view/HippyViewManager.mm +++ b/renderer/native/ios/renderer/component/view/HippyViewManager.mm @@ -178,6 +178,30 @@ - (void)measureInAppWindow:(NSNumber *)componentTag }]; } +HIPPY_EXPORT_METHOD(getViewTagByLocation:(nonnull NSNumber *)componentTag + params:(NSDictionary *__nonnull)params + callback:(HippyPromiseResolveBlock)callback) { + [self.bridge.uiManager addUIBlock:^(__unused HippyUIManager *uiManager, NSDictionary *viewRegistry) { + UIView *view = viewRegistry[componentTag]; + if (view == nil) { + callback(@[]); + return; + } + UIView *rootView = viewRegistry[view.rootTag]; + if (rootView == nil) { + callback(@[]); + return; + } + double locationX = [params[@"xOnScreen"] doubleValue]; + double locationY = [params[@"yOnScreen"] doubleValue]; + UIView* hitView = [rootView hitTest:{locationX, locationY} withEvent:nil]; + NSDictionary *locationDict = @{ + @"hippyTag": hitView.hippyTag, + }; + callback(@[locationDict]); + }]; +} + HIPPY_EXPORT_METHOD(getLocationOnScreen:(nonnull NSNumber *)componentTag params:(NSDictionary *__nonnull)params callback:(HippyPromiseResolveBlock)callback) { @@ -187,12 +211,19 @@ - (void)measureInAppWindow:(NSNumber *)componentTag callback(@[]); return; } + UIView *rootView = viewRegistry[view.rootTag]; + CGFloat rootX = 0, rootY = 0; + if (rootView) { + CGRect windowFrame = [rootView.window convertRect:rootView.frame fromView:rootView.superview]; + rootX = windowFrame.origin.x; + rootY = windowFrame.origin.y; + } CGRect windowFrame = [view.window convertRect:view.frame fromView:view.superview]; NSDictionary *locationDict = @{ - @"xOnScreen": @(static_cast(windowFrame.origin.x)), - @"yOnScreen": @(static_cast(windowFrame.origin.y)), - @"viewWidth": @(static_cast(CGRectGetHeight(windowFrame))), - @"viewHeight": @(static_cast(CGRectGetWidth(windowFrame))) + @"xOnScreen": @(static_cast(windowFrame.origin.x - rootX)), + @"yOnScreen": @(static_cast(windowFrame.origin.y - rootY)), + @"viewWidth": @(static_cast(CGRectGetWidth(windowFrame))), + @"viewHeight": @(static_cast(CGRectGetHeight(windowFrame))) }; callback(@[locationDict]); }];