From c59bdfddcf93619f8323187d0f1a7309276d3a06 Mon Sep 17 00:00:00 2001 From: ShouruiSong Date: Thu, 6 Aug 2026 18:40:42 +0800 Subject: [PATCH] [Feature] Add indexed Element mutation APIs - Add indexed insert, contiguous removal, and pre-change-coordinate move operations to the Element API. - Reuse existing Element API argument checks and validate tree ranges before mutation. - Expose generated native bindings and TypeScript declarations for the new APIs. TEST: Built renderer bindings; ran dom_unittest_exec and renderer_function_ng_c_api_unittests_exec AutoSubmit: true --- core/runtime/lepus/bindings/renderer.h | 3 + .../lepus/bindings/renderer_functions.cc | 171 ++++++++++++++++++ .../lepus/bindings/renderer_functions_def.h | 3 + core/runtime/lepusng/bindings/renderer_ng.cc | 3 + core/runtime/trace/runtime_trace_event_def.h | 14 ++ .../type-element-api/types/element-api.d.ts | 6 + 6 files changed, 200 insertions(+) diff --git a/core/runtime/lepus/bindings/renderer.h b/core/runtime/lepus/bindings/renderer.h index e27ea51ca1..dff5beb45b 100644 --- a/core/runtime/lepus/bindings/renderer.h +++ b/core/runtime/lepus/bindings/renderer.h @@ -156,6 +156,9 @@ constexpr static const char* kCFunctionAppendElement = "__AppendElement"; constexpr static const char* kCFunctionRemoveElement = "__RemoveElement"; constexpr static const char* kCFunctionInsertElementBefore = "__InsertElementBefore"; +constexpr static const char* kCFunctionInsertElementAt = "__InsertElementAt"; +constexpr static const char* kCFunctionRemoveElementsAt = "__RemoveElementsAt"; +constexpr static const char* kCFunctionMoveElements = "__MoveElements"; constexpr static const char* kCFunctionFirstElement = "__FirstElement"; constexpr static const char* kCFunctionLastElement = "__LastElement"; constexpr static const char* kCFunctionNextElement = "__NextElement"; diff --git a/core/runtime/lepus/bindings/renderer_functions.cc b/core/runtime/lepus/bindings/renderer_functions.cc index 65b62aa793..17ba13b603 100644 --- a/core/runtime/lepus/bindings/renderer_functions.cc +++ b/core/runtime/lepus/bindings/renderer_functions.cc @@ -3198,6 +3198,177 @@ RENDERER_FUNCTION_CC(FiberInsertElementBefore) { RETURN(lepus::Value(std::move(child))); } +RENDERER_FUNCTION_CC(FiberInsertElementAt) { + TRACE_EVENT(LYNX_TRACE_CATEGORY, FIBER_INSERT_ELEMENT_AT); + // parameter size = 3 + // [0] Element -> parent element + // [1] Element -> child element + // [2] Number -> index + CHECK_ARGC_GE(FiberInsertElementAt, 3); + CONVERT_ARG_AND_CHECK_FOR_ELEMENT_API(arg0, 0, RefCounted, + FiberInsertElementAt); + CONVERT_ARG_AND_CHECK_FOR_ELEMENT_API(arg1, 1, RefCounted, + FiberInsertElementAt); + CONVERT_ARG_AND_CHECK_FOR_ELEMENT_API(arg2, 2, Number, FiberInsertElementAt); + + auto parent = GetFiberElementFromValue(*arg0); + auto child = GetFiberElementFromValue(*arg1); + if (parent == nullptr || child == nullptr) { + ElementAPIError("FiberInsertElementAt parent and child should be Element"); + RETURN_UNDEFINED(); + } + + const auto index = static_cast(arg2->Number()); + const int64_t child_count = static_cast(parent->GetChildCount()); + if (index < 0 || static_cast(index) > child_count) { + ElementAPIError( + "FiberInsertElementAt index is out of bounds, index: %d, size: %lld", + index, static_cast(child_count)); + RETURN_UNDEFINED(); + } + + parent->InsertNode(child, index); + ON_NODE_ADDED(child); + RETURN_UNDEFINED(); +} + +RENDERER_FUNCTION_CC(FiberRemoveElementsAt) { + TRACE_EVENT(LYNX_TRACE_CATEGORY, FIBER_REMOVE_ELEMENTS_AT); + // parameter size = 3 + // [0] Element -> parent element + // [1] Number -> index + // [2] Number -> count + CHECK_ARGC_GE(FiberRemoveElementsAt, 3); + CONVERT_ARG_AND_CHECK_FOR_ELEMENT_API(arg0, 0, RefCounted, + FiberRemoveElementsAt); + CONVERT_ARG_AND_CHECK_FOR_ELEMENT_API(arg1, 1, Number, FiberRemoveElementsAt); + CONVERT_ARG_AND_CHECK_FOR_ELEMENT_API(arg2, 2, Number, FiberRemoveElementsAt); + + auto parent = GetFiberElementFromValue(*arg0); + if (parent == nullptr) { + ElementAPIError("FiberRemoveElementsAt parent should be Element"); + RETURN_UNDEFINED(); + } + + const auto index = static_cast(arg1->Number()); + const auto count = static_cast(arg2->Number()); + const int64_t child_count = static_cast(parent->GetChildCount()); + const int64_t range_end = + static_cast(index) + static_cast(count); + if (index < 0 || count < 0 || static_cast(index) > child_count || + range_end > child_count) { + ElementAPIError( + "FiberRemoveElementsAt range is out of bounds, index: %d, count: %d, " + "size: %lld", + index, count, static_cast(child_count)); + RETURN_UNDEFINED(); + } + + base::InlineVector, 16> removed_elements; + for (int32_t offset = 0; offset < count; ++offset) { + const auto child_index = + static_cast(static_cast(index) + offset); + auto* child = parent->GetChildAt(child_index); + if (child == nullptr) { + ElementAPIError( + "FiberRemoveElementsAt failed to resolve the complete child range"); + RETURN_UNDEFINED(); + } + removed_elements.emplace_back(fml::RefPtr(child)); + } + + for (const auto& child : removed_elements) { + ON_NODE_REMOVED(child); + parent->RemoveNode(child); + } + RETURN_UNDEFINED(); +} + +RENDERER_FUNCTION_CC(FiberMoveElements) { + TRACE_EVENT(LYNX_TRACE_CATEGORY, FIBER_MOVE_ELEMENTS); + // parameter size = 4 + // [0] Element -> parent element + // [1] Number -> source index in the pre-change sequence + // [2] Number -> destination index in the pre-change sequence + // [3] Number -> count + CHECK_ARGC_GE(FiberMoveElements, 4); + CONVERT_ARG_AND_CHECK_FOR_ELEMENT_API(arg0, 0, RefCounted, FiberMoveElements); + CONVERT_ARG_AND_CHECK_FOR_ELEMENT_API(arg1, 1, Number, FiberMoveElements); + CONVERT_ARG_AND_CHECK_FOR_ELEMENT_API(arg2, 2, Number, FiberMoveElements); + CONVERT_ARG_AND_CHECK_FOR_ELEMENT_API(arg3, 3, Number, FiberMoveElements); + + auto parent = GetFiberElementFromValue(*arg0); + if (parent == nullptr) { + ElementAPIError("FiberMoveElements parent should be Element"); + RETURN_UNDEFINED(); + } + + const auto from = static_cast(arg1->Number()); + const auto to = static_cast(arg2->Number()); + const auto count = static_cast(arg3->Number()); + const int64_t child_count = static_cast(parent->GetChildCount()); + const int64_t source_end = + static_cast(from) + static_cast(count); + if (from < 0 || to < 0 || count < 0 || + static_cast(from) > child_count || + static_cast(to) > child_count || source_end > child_count) { + ElementAPIError( + "FiberMoveElements range is out of bounds, from: %d, to: %d, count: " + "%d, size: %lld", + from, to, count, static_cast(child_count)); + RETURN_UNDEFINED(); + } + + if (count == 0 || from == to || static_cast(to) == source_end) { + RETURN_UNDEFINED(); + } + if (from < to && static_cast(to) < source_end) { + ElementAPIError( + "FiberMoveElements destination overlaps the source range, from: %d, " + "to: %d, count: %d", + from, to, count); + RETURN_UNDEFINED(); + } + + const int64_t destination = + from > to ? static_cast(to) + : static_cast(to) - static_cast(count); + const int64_t destination_end = destination + static_cast(count); + if (destination < 0 || destination > child_count - count || + destination_end - 1 > std::numeric_limits::max()) { + ElementAPIError( + "FiberMoveElements destination is out of bounds, from: %d, to: %d, " + "count: %d, destination: %lld, size: %lld", + from, to, count, static_cast(destination), + static_cast(child_count)); + RETURN_UNDEFINED(); + } + + base::InlineVector, 16> moving_elements; + for (int32_t offset = 0; offset < count; ++offset) { + const auto child_index = + static_cast(static_cast(from) + offset); + auto* child = parent->GetChildAt(child_index); + if (child == nullptr) { + ElementAPIError( + "FiberMoveElements failed to resolve the complete source range"); + RETURN_UNDEFINED(); + } + moving_elements.emplace_back(fml::RefPtr(child)); + } + + for (const auto& child : moving_elements) { + ON_NODE_REMOVED(child); + parent->RemoveNode(child, false); + } + for (int32_t offset = 0; offset < count; ++offset) { + const auto& child = moving_elements[offset]; + parent->InsertNode(child, static_cast(destination + offset)); + ON_NODE_ADDED(child); + } + RETURN_UNDEFINED(); +} + RENDERER_FUNCTION_CC(FiberFirstElement) { TRACE_EVENT(LYNX_TRACE_CATEGORY, FIBER_FIRST_ELEMENT); // parameter size = 1 diff --git a/core/runtime/lepus/bindings/renderer_functions_def.h b/core/runtime/lepus/bindings/renderer_functions_def.h index 06c80ed5ea..d9e0dc3569 100644 --- a/core/runtime/lepus/bindings/renderer_functions_def.h +++ b/core/runtime/lepus/bindings/renderer_functions_def.h @@ -79,6 +79,9 @@ V(FiberAppendElement) \ V(FiberRemoveElement) \ V(FiberInsertElementBefore) \ + V(FiberInsertElementAt) \ + V(FiberRemoveElementsAt) \ + V(FiberMoveElements) \ V(FiberFirstElement) \ V(FiberLastElement) \ V(FiberNextElement) \ diff --git a/core/runtime/lepusng/bindings/renderer_ng.cc b/core/runtime/lepusng/bindings/renderer_ng.cc index 21946b6cad..7f17756dde 100644 --- a/core/runtime/lepusng/bindings/renderer_ng.cc +++ b/core/runtime/lepusng/bindings/renderer_ng.cc @@ -466,6 +466,9 @@ const runtime::RenderBindingFunction* Renderer::GetBuiltinFunctionsForFiber( /* 130 */ {kCFunctionRemoveNodeFromElementTemplate, &RendererFunctions::FiberRemoveNodeFromElementTemplate, true, true}, /* 131 */ {kCFunctionSerializeElementTemplate, &RendererFunctions::FiberSerializeElementTemplate, true, true}, /* 132 */ {kCFunctionCreateTypedElementTemplate, &RendererFunctions::FiberCreateTypedElementTemplate, true, true}, + /* 133 */ {kCFunctionInsertElementAt, &RendererFunctions::FiberInsertElementAt, true, true}, + /* 134 */ {kCFunctionRemoveElementsAt, &RendererFunctions::FiberRemoveElementsAt, true, true}, + /* 135 */ {kCFunctionMoveElements, &RendererFunctions::FiberMoveElements, true, true}, }; // clang-format on size = sizeof(kFuncs) / sizeof(kFuncs[0]); diff --git a/core/runtime/trace/runtime_trace_event_def.h b/core/runtime/trace/runtime_trace_event_def.h index 4a1b8c9200..aa52bb50fc 100644 --- a/core/runtime/trace/runtime_trace_event_def.h +++ b/core/runtime/trace/runtime_trace_event_def.h @@ -833,6 +833,20 @@ inline constexpr const char* const FIBER_REMOVE_ELEMENT = "FiberRemoveElement"; */ inline constexpr const char* const FIBER_INSERT_ELEMENT_BEFORE = "FiberInsertElementBefore"; +/** + * @trace_description: Insert a Fiber element at a child index. + */ +inline constexpr const char* const FIBER_INSERT_ELEMENT_AT = + "FiberInsertElementAt"; +/** + * @trace_description: Remove Fiber elements from a child index range. + */ +inline constexpr const char* const FIBER_REMOVE_ELEMENTS_AT = + "FiberRemoveElementsAt"; +/** + * @trace_description: Move Fiber elements between child indexes. + */ +inline constexpr const char* const FIBER_MOVE_ELEMENTS = "FiberMoveElements"; /** * @trace_description: Get the first child element of a Fiber element. */ diff --git a/js_libraries/type-element-api/types/element-api.d.ts b/js_libraries/type-element-api/types/element-api.d.ts index dcc16fa310..2d9ed1e405 100644 --- a/js_libraries/type-element-api/types/element-api.d.ts +++ b/js_libraries/type-element-api/types/element-api.d.ts @@ -195,6 +195,12 @@ declare global { function __InsertElementBefore(parent: ElementRef, current: ElementRef, marker?: ElementRef): ElementRef; + function __InsertElementAt(parent: ElementRef, current: ElementRef, index: number): void; + + function __RemoveElementsAt(parent: ElementRef, index: number, count: number): void; + + function __MoveElements(parent: ElementRef, from: number, to: number, count: number): void; + function __SwapElement(left: ElementRef, right: ElementRef): void; function __ReplaceElement(newElement: ElementRef, oldElement: ElementRef): void;