Skip to content

Commit

Permalink
fix(devtools): fix the mapping between DOM tree and UI inspector
Browse files Browse the repository at this point in the history
  • Loading branch information
Cyunong committed Nov 12, 2024
1 parent 8dee058 commit 8766a0d
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 42 deletions.
4 changes: 2 additions & 2 deletions devtools/devtools-backend/src/module/domain/dom_domain.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand All @@ -45,7 +46,7 @@ class DevToolsUtil {
uint32_t depth,
const std::shared_ptr<DomManager>& dom_manager);

static DomNodeLocation GetNodeIdByDomLocation(const std::shared_ptr<DomNode>& root_node, double x, double y);
static DomNodeLocation GetNodeIdByDomLocation(const std::shared_ptr<RootNode>& root_node, double x, double y);

static DomPushNodePathMetas GetPushNodeByPath(const std::shared_ptr<DomNode>& dom_node,
std::vector<std::map<std::string, int32_t>> path);
Expand All @@ -55,8 +56,8 @@ class DevToolsUtil {
static bool ShouldAvoidPostDomManagerTask(const std::string& event_name);

private:
static std::shared_ptr<DomNode> GetHitNode(const std::shared_ptr<DomNode>& root_node, const std::shared_ptr<DomNode>& node, double x, double y);
static bool IsLocationHitNode(const std::shared_ptr<DomNode>& root_node, const std::shared_ptr<DomNode>& dom_node, double x, double y);
static hippy::dom::DomArgument makeLocationArgument(double x, double y);
static std::shared_ptr<DomNode> GetHitNode(const std::shared_ptr<RootNode>& 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<std::string, HippyValue>& node_props);
Expand Down
92 changes: 59 additions & 33 deletions devtools/devtools-integration/native/src/devtools_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -91,8 +93,8 @@ DomainMetas DevToolsUtil::GetDomDomainData(const std::shared_ptr<DomNode>& root_
return metas;
}

DomNodeLocation DevToolsUtil::GetNodeIdByDomLocation(const std::shared_ptr<DomNode>& root_node, double x, double y) {
auto hit_node = GetHitNode(root_node, root_node, x, y);
DomNodeLocation DevToolsUtil::GetNodeIdByDomLocation(const std::shared_ptr<RootNode>& 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;
Expand Down Expand Up @@ -127,36 +129,6 @@ DomPushNodePathMetas DevToolsUtil::GetPushNodeByPath(const std::shared_ptr<DomNo
return metas;
}

std::shared_ptr<DomNode> DevToolsUtil::GetHitNode(const std::shared_ptr<DomNode>& root_node, const std::shared_ptr<DomNode>& node, double x, double y) {
if (node == nullptr || !IsLocationHitNode(root_node, node, x, y)) {
return nullptr;
}
std::shared_ptr<DomNode> 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<DomNode>& root_node, const std::shared_ptr<DomNode>& dom_node, double x, double y) {
LayoutResult layout_result = GetLayoutOnScreen(root_node, dom_node);
double self_x = static_cast<uint32_t>(layout_result.left);
double self_y = static_cast<uint32_t>(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 <class F>
auto MakeCopyable(F&& f) {
auto s = std::make_shared<std::decay_t<F>>(std::forward<F>(f));
Expand All @@ -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<DomNode> DevToolsUtil::GetHitNode(const std::shared_ptr<RootNode>& root_node, double x, double y) {
std::shared_ptr<DomNode> base_node = nullptr;
auto children = root_node->GetChildren();
if (!children.empty()) {
base_node = children[0];
}
std::promise<int> layout_promise;
std::future<int> 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<hippy::dom::DomArgument> 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<int>(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<DomNode> targetNode = base_node;
int hippyTag = read_file_future.get();
if (hippyTag > 0) {
targetNode = root_node->GetNode(static_cast<uint32_t>(hippyTag));
}
return targetNode;
}

LayoutResult DevToolsUtil::GetLayoutOnScreen(const std::shared_ptr<DomNode>& root_node, const std::shared_ptr<DomNode>& dom_node) {
std::shared_ptr<DomNode> find_node = nullptr;
if (dom_node == root_node) {
Expand Down Expand Up @@ -419,7 +445,7 @@ void DevToolsUtil::PostDomTask(const std::weak_ptr<DomManager>& 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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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;
}
Expand All @@ -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<String, Object> 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) {
Expand Down
39 changes: 35 additions & 4 deletions renderer/native/ios/renderer/component/view/HippyViewManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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<NSNumber *, UIView *> *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) {
Expand All @@ -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<int>(windowFrame.origin.x)),
@"yOnScreen": @(static_cast<int>(windowFrame.origin.y)),
@"viewWidth": @(static_cast<int>(CGRectGetHeight(windowFrame))),
@"viewHeight": @(static_cast<int>(CGRectGetWidth(windowFrame)))
@"xOnScreen": @(static_cast<int>(windowFrame.origin.x - rootX)),
@"yOnScreen": @(static_cast<int>(windowFrame.origin.y - rootY)),
@"viewWidth": @(static_cast<int>(CGRectGetWidth(windowFrame))),
@"viewHeight": @(static_cast<int>(CGRectGetHeight(windowFrame)))
};
callback(@[locationDict]);
}];
Expand Down

0 comments on commit 8766a0d

Please sign in to comment.