From e091b54a3e3af178400fe0ee2468794a66517d47 Mon Sep 17 00:00:00 2001 From: ShouruiSong Date: Fri, 26 Jun 2026 15:18:33 +0800 Subject: [PATCH] [Optimize] Use platform-owned native render resource ids Native UI Render text and image display-list operations now reference platform-owned paint resource ids instead of fragment ids. Fragment behaviors keep the last image request key or text bundle identity and request a new platform resource only when that identity changes. Android and iOS now keep resource registries in their renderer contexts, preserve owner ids for event and legacy lookup paths, and let display-list appliers release text and image resources after active display lists stop referencing them. The Fragment Layer Render architecture note and platform API snapshots were updated to match the new contract. TEST: tools/env.sh clang-format -i for changed C++, Objective-C++, and Java files TEST: tools/env.sh git lynx check TEST: ./out/Default/fragment_test_exec --gtest_filter=DisplayListBuilderTest.DrawImageOperation:DisplayListBuilderTest.DrawImageWithBoxIndex TEST: ./out/Default/dom_unittest_exec --gtest_filter=FragmentTest.TestUpdateLayoutAndDefineBoxAndDrawImage:TextFragmentBehaviorTest.* TEST: tools/env.sh ninja -C out/Default dom_unittest_exec TEST: ANDROID_SERIAL=11161FDD4001F3 ./gradlew :LynxAndroid:connectedNoasanDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.lynx.tasm.behavior.render.DisplayListApplierTest built Java and native targets, then device install was blocked by INSTALL_FAILED_DEPRECATED_SDK_VERSION because the package targetSdk is 22 on Android 14 TEST: xcodebuild build-for-testing for LynxRendererContextUnitTest and LynxDisplayListApplierUnitTest was attempted, but the workspace failed while copying missing source header lynx/core/runtime/js/bindings/api_invoke_ctrl.h referenced by Pods/Lynx.xcodeproj AutoSubmit: true --- .../ai/architecture/fragment_layer_render.md | 10 +- .../renderer/dom/fragment/fragment_behavior.h | 19 ++-- .../dom/fragment/fragment_unittest.cc | 5 + .../dom/fragment/image_fragment_behavior.cc | 7 +- .../dom/fragment/image_fragment_behavior.h | 1 + .../dom/fragment/text_fragment_behavior.cc | 22 ++-- .../dom/fragment/text_fragment_behavior.h | 2 + .../native_painting_context_android.cc | 25 +++-- .../android/native_painting_context_android.h | 8 +- .../android/platform_renderer_context.cc | 30 +++-- .../android/platform_renderer_context.h | 12 +- .../ios/native_painting_context_darwin.h | 10 +- .../ios/native_painting_context_darwin.mm | 29 +++-- .../painting/native_painting_context.h | 18 ++- platform/android/api/lynx_android.api | 8 +- .../render/DisplayListApplierTest.java | 24 ++++ .../behavior/render/DisplayListApplier.java | 68 +++++++++++- .../render/PlatformRendererContext.java | 104 ++++++++++++++---- .../lynx/tasm/behavior/render/Renderer.java | 7 +- platform/darwin/ios/api/lynx_ios.api | 9 +- .../lynx/renderer/LynxDisplayListApplier.mm | 81 +++++++++++++- .../LynxDisplayListApplierUnitTest.mm | 30 +++++ .../ios/lynx/renderer/LynxRendererContext.h | 9 +- .../ios/lynx/renderer/LynxRendererContext.mm | 64 ++++++++--- 24 files changed, 470 insertions(+), 132 deletions(-) diff --git a/core/renderer/dom/fragment/ai/architecture/fragment_layer_render.md b/core/renderer/dom/fragment/ai/architecture/fragment_layer_render.md index 10f3bad1d3..10f597d960 100644 --- a/core/renderer/dom/fragment/ai/architecture/fragment_layer_render.md +++ b/core/renderer/dom/fragment/ai/architecture/fragment_layer_render.md @@ -96,6 +96,12 @@ struct OpData { | kRecordBox | 11 | Record Box Model (border/padding/content) | | kLinearGradient | 12 | Draw linear gradient | +For `kText` and `kImage`, the first integer argument is a platform paint +resource id returned by `NativePaintingContext`, not the Fragment/node id. The +Fragment id remains the owner id for events and lifecycle bookkeeping, while the +display list references the concrete text/image resource for the current content +snapshot. + #### Subtree Property Types (DisplayListSubtreePropertyOpType) | Type | Value | Description | @@ -348,14 +354,14 @@ The iOS platform parses DisplayList through **direct memory access**. The C++ la break; } case DisplayListOpType::kText: { - auto text_id = [self nextContentInt]; + auto text_id = [self nextContentInt]; // platform text resource id auto box_index = [self nextContentInt]; // Get TextRenderer from LynxRendererContext to create LynxTextLayer LynxTextLayer *layer = [[LynxTextLayer alloc] initWithLynxTextRenderer:...]; break; } case DisplayListOpType::kImage: { - auto image_id = [self nextContentInt]; + auto image_id = [self nextContentInt]; // platform image resource id auto box_index = [self nextContentInt]; // Create UIImageView and load image through LynxImageManager UIImageView *imageView = [self createImageView]; diff --git a/core/renderer/dom/fragment/fragment_behavior.h b/core/renderer/dom/fragment/fragment_behavior.h index 4d294ed81c..82254168b1 100644 --- a/core/renderer/dom/fragment/fragment_behavior.h +++ b/core/renderer/dom/fragment/fragment_behavior.h @@ -29,10 +29,12 @@ class Fragment; // Fragments are destroyed. This creates a use-after-free if subclasses try to // access painting_context_ in their destructors. // -// CORRECT APPROACH: Override OnElementDestroying() to release all platform -// resources (native views, text bundles, etc.). This method is called by -// Fragment::Destroy() while ElementManager and PaintingContext are still alive -// and all pointers remain valid. +// CORRECT APPROACH: Override OnElementDestroying() to release platform +// resources owned directly by this behavior. Display-list paint resources such +// as Text and Image are released by the platform display-list consumer after +// the active display list stops referencing their resource ids. This method is +// called by Fragment::Destroy() while ElementManager and PaintingContext are +// still alive and all pointers remain valid. // // Example: // class MyBehavior : public FragmentBehavior { @@ -56,10 +58,11 @@ class FragmentBehavior { // Called by Fragment::Destroy() when the element is being destroyed. // - // This is the ONLY safe place to release platform resources (native views, - // text bundles, image resources, etc.). At this point, ElementManager and - // PaintingContext are still alive, so painting_context_ and fragment_ - // pointers remain valid. + // This is the ONLY safe place to release directly-owned platform resources. + // Display-list paint resources are released by the platform display-list + // consumer after their resource ids disappear from the active display list. + // At this point, ElementManager and PaintingContext are still alive, so + // painting_context_ and fragment_ pointers remain valid. // // DO NOT wait until the destructor to clean up resources - by then the // PaintingContext may have already been destroyed, causing use-after-free diff --git a/core/renderer/dom/fragment/fragment_unittest.cc b/core/renderer/dom/fragment/fragment_unittest.cc index 59803d7762..89c604a42f 100644 --- a/core/renderer/dom/fragment/fragment_unittest.cc +++ b/core/renderer/dom/fragment/fragment_unittest.cc @@ -742,6 +742,9 @@ TEST_F(FragmentTest, TestUpdateLayoutAndDefineBoxAndDrawImage) { EXPECT_EQ(fragment.DefinePaddingBox(builder), 1); EXPECT_EQ(fragment.DefineContentBox(builder), 2); + auto* image_behavior = + static_cast(fragment.behavior_.get()); + image_behavior->image_resource_id_ = 123; fragment.behavior_->OnDraw(builder); DisplayList list = builder.Build(); @@ -810,6 +813,8 @@ TEST_F(FragmentTest, TestUpdateLayoutAndDefineBoxAndDrawImage) { EXPECT_EQ(ops[3], static_cast(DisplayListOpType::kImage)); EXPECT_EQ(ints[6], 2); EXPECT_EQ(ints[7], 0); + EXPECT_EQ(ints[8], 123); + EXPECT_EQ(ints[9], 2); } TEST_F(FragmentTest, TestCheckRootIfNeedClipBounds) { diff --git a/core/renderer/dom/fragment/image_fragment_behavior.cc b/core/renderer/dom/fragment/image_fragment_behavior.cc index 48d6b5e48c..e905add556 100644 --- a/core/renderer/dom/fragment/image_fragment_behavior.cc +++ b/core/renderer/dom/fragment/image_fragment_behavior.cc @@ -42,7 +42,7 @@ void ImageFragmentBehavior::OnUpdateLayout( if (event_mask_ < 0) { event_mask_ = ComputeEventMask(); } - painting_context_->CreateImage( + image_resource_id_ = painting_context_->CreateImageResource( fragment_->id(), image_url_, layout_info.GetContentBoxWidth(), layout_info.GetContentBoxHeight(), event_mask_); fragment_->InvalidateForRedraw(); @@ -50,8 +50,11 @@ void ImageFragmentBehavior::OnUpdateLayout( } void ImageFragmentBehavior::OnDraw(DisplayListBuilder& display_list_builder) { + if (image_resource_id_ == kInvalidPlatformResourceId) { + return; + } display_list_builder.DrawImage( - fragment()->id(), fragment()->DefineContentBox(display_list_builder)); + image_resource_id_, fragment()->DefineContentBox(display_list_builder)); } } // namespace lynx::tasm diff --git a/core/renderer/dom/fragment/image_fragment_behavior.h b/core/renderer/dom/fragment/image_fragment_behavior.h index 80a4ec204e..03f5068e4b 100644 --- a/core/renderer/dom/fragment/image_fragment_behavior.h +++ b/core/renderer/dom/fragment/image_fragment_behavior.h @@ -31,6 +31,7 @@ class ImageFragmentBehavior : public FragmentBehavior { int32_t ComputeEventMask() const; base::String image_url_; + int32_t image_resource_id_{kInvalidPlatformResourceId}; // Cached event mask - computed lazily on first use, then never changes. mutable int32_t event_mask_{-1}; // -1 means not yet computed }; diff --git a/core/renderer/dom/fragment/text_fragment_behavior.cc b/core/renderer/dom/fragment/text_fragment_behavior.cc index 5d06fe8f1f..6669a3b766 100644 --- a/core/renderer/dom/fragment/text_fragment_behavior.cc +++ b/core/renderer/dom/fragment/text_fragment_behavior.cc @@ -28,11 +28,9 @@ TextFragmentBehavior::TextFragmentBehavior(Fragment* fragment) : FragmentBehavior(fragment) {} void TextFragmentBehavior::OnElementDestroying() { - // Release platform resources while element is still accessible - if (painting_context_ && text_bundle_ != 0) { - painting_context_->DestroyTextBundle(fragment_->id()); - text_bundle_ = 0; - } + text_bundle_ = 0; + last_text_bundle_ = 0; + text_resource_id_ = kInvalidPlatformResourceId; } void TextFragmentBehavior::CreatePlatformRenderer( @@ -45,8 +43,14 @@ void TextFragmentBehavior::CreatePlatformRenderer( void TextFragmentBehavior::OnUpdateLayout( const LayoutInfoForDraw& layout_result) { - if (painting_context_ && fragment_) { - painting_context_->UpdateTextBundle(fragment_->id(), text_bundle_); + if (painting_context_ && fragment_ && text_bundle_ != last_text_bundle_) { + last_text_bundle_ = text_bundle_; + if (text_bundle_ == 0) { + text_resource_id_ = kInvalidPlatformResourceId; + } else { + text_resource_id_ = + painting_context_->CreateTextResource(fragment_->id(), text_bundle_); + } } DispatchLayoutEvent(layout_result); } @@ -58,7 +62,9 @@ void TextFragmentBehavior::OnDraw(DisplayListBuilder& builder) { layout_info.layout_result.padding_[starlight::Direction::kTop], layout_info.layout_result.size_.width_, layout_info.layout_result.size_.height_); - builder.DrawText(fragment_->id(), fragment_->DefineBorderBox(builder)); + if (text_resource_id_ != kInvalidPlatformResourceId) { + builder.DrawText(text_resource_id_, fragment_->DefineBorderBox(builder)); + } builder.End(); } diff --git a/core/renderer/dom/fragment/text_fragment_behavior.h b/core/renderer/dom/fragment/text_fragment_behavior.h index 4c3b810b33..9e4f8a6e2f 100644 --- a/core/renderer/dom/fragment/text_fragment_behavior.h +++ b/core/renderer/dom/fragment/text_fragment_behavior.h @@ -25,6 +25,8 @@ class TextFragmentBehavior : public FragmentBehavior { private: intptr_t text_bundle_{0}; + intptr_t last_text_bundle_{0}; + int32_t text_resource_id_{kInvalidPlatformResourceId}; void DispatchLayoutEvent(const LayoutInfoForDraw& layout_result); }; } // namespace lynx::tasm diff --git a/core/renderer/ui_wrapper/painting/android/native_painting_context_android.cc b/core/renderer/ui_wrapper/painting/android/native_painting_context_android.cc index 1e56de149b..8df0946875 100644 --- a/core/renderer/ui_wrapper/painting/android/native_painting_context_android.cc +++ b/core/renderer/ui_wrapper/painting/android/native_painting_context_android.cc @@ -490,24 +490,25 @@ void NativePaintingCtxAndroid::ReconstructEventTargetTreeRecursively() { }); } -void NativePaintingCtxAndroid::CreateImage(int id, base::String src, - float width, float height, - int32_t event_mask) { +int32_t NativePaintingCtxAndroid::CreateImageResource(int owner_id, + base::String src, + float width, float height, + int32_t event_mask) { + int32_t resource_id = NextPlatformResourceId(); if (view_manager_) { - view_manager_->CreateImage(id, src, width, height, event_mask); + view_manager_->CreateImageResource(resource_id, owner_id, src, width, + height, event_mask); } + return resource_id; } -void NativePaintingCtxAndroid::UpdateTextBundle(int id, intptr_t bundle) { +int32_t NativePaintingCtxAndroid::CreateTextResource(int owner_id, + intptr_t bundle) { + int32_t resource_id = NextPlatformResourceId(); if (view_manager_) { - view_manager_->UpdateTextBundle(id, bundle); - } -} - -void NativePaintingCtxAndroid::DestroyTextBundle(int id) { - if (view_manager_) { - view_manager_->DestroyTextBundle(id); + view_manager_->CreateTextResource(resource_id, owner_id, bundle); } + return resource_id; } void NativePaintingCtxAndroid::InsertListItemPaintingNode(int32_t list_id, diff --git a/core/renderer/ui_wrapper/painting/android/native_painting_context_android.h b/core/renderer/ui_wrapper/painting/android/native_painting_context_android.h index e07a219251..eb347b7fd4 100644 --- a/core/renderer/ui_wrapper/painting/android/native_painting_context_android.h +++ b/core/renderer/ui_wrapper/painting/android/native_painting_context_android.h @@ -117,12 +117,10 @@ class NativePaintingCtxAndroid : public PaintingCtxPlatformImpl, void UpdatePlatformEventBundle(int32_t id, PlatformEventBundle bundle) override; - void CreateImage(int id, base::String src, float width, float height, - int32_t event_mask = 0) override; + int32_t CreateImageResource(int owner_id, base::String src, float width, + float height, int32_t event_mask = 0) override; - void UpdateTextBundle(int id, intptr_t bundle) override; - - void DestroyTextBundle(int id) override; + int32_t CreateTextResource(int owner_id, intptr_t bundle) override; void InsertListItemPaintingNode(int32_t list_id, int32_t child_id) override; diff --git a/core/renderer/ui_wrapper/painting/android/platform_renderer_context.cc b/core/renderer/ui_wrapper/painting/android/platform_renderer_context.cc index 84b3f3762d..3d3274a6a6 100644 --- a/core/renderer/ui_wrapper/painting/android/platform_renderer_context.cc +++ b/core/renderer/ui_wrapper/painting/android/platform_renderer_context.cc @@ -110,9 +110,11 @@ void PlatformRendererContext::DestroyPlatformRenderer(int32_t target) { UnregisterPlatformRenderer(target); } -void PlatformRendererContext::CreateImage(int32_t id, base::String src, - float width, float height, - int32_t event_mask) { +void PlatformRendererContext::CreateImageResource(int32_t resource_id, + int32_t owner_id, + base::String src, float width, + float height, + int32_t event_mask) { base::android::ScopedLocalJavaRef local_ref(java_ref_); if (local_ref.IsNull()) { return; @@ -121,37 +123,41 @@ void PlatformRendererContext::CreateImage(int32_t id, base::String src, auto j_src = base::android::JNIConvertHelper::ConvertToJNIStringUTF(env, src.c_str()); Java_PlatformRendererContext_createImage( - env, local_ref.Get(), id, j_src.Get(), static_cast(width), - static_cast(height), static_cast(event_mask)); + env, local_ref.Get(), resource_id, owner_id, j_src.Get(), + static_cast(width), static_cast(height), + static_cast(event_mask)); } -void PlatformRendererContext::DestroyImage(int32_t id) { +void PlatformRendererContext::DestroyImageResource(int32_t resource_id) { base::android::ScopedLocalJavaRef local_ref(java_ref_); if (local_ref.IsNull()) { return; } JNIEnv* env = base::android::AttachCurrentThread(); - Java_PlatformRendererContext_destroyImage(env, local_ref.Get(), id); + Java_PlatformRendererContext_destroyImage(env, local_ref.Get(), resource_id); } -void PlatformRendererContext::UpdateTextBundle(int32_t id, - intptr_t text_bundle) { +void PlatformRendererContext::CreateTextResource(int32_t resource_id, + int32_t owner_id, + intptr_t text_bundle) { base::android::ScopedLocalJavaRef local_ref(java_ref_); if (local_ref.IsNull()) { return; } JNIEnv* env = base::android::AttachCurrentThread(); Java_PlatformRendererContext_updateTextBundle( - env, local_ref.Get(), id, static_cast(text_bundle)); + env, local_ref.Get(), resource_id, owner_id, + static_cast(text_bundle)); } -void PlatformRendererContext::DestroyTextBundle(int32_t id) { +void PlatformRendererContext::DestroyTextResource(int32_t resource_id) { base::android::ScopedLocalJavaRef local_ref(java_ref_); if (local_ref.IsNull()) { return; } JNIEnv* env = base::android::AttachCurrentThread(); - Java_PlatformRendererContext_destroyTextBundle(env, local_ref.Get(), id); + Java_PlatformRendererContext_destroyTextBundle(env, local_ref.Get(), + resource_id); } void PlatformRendererContext::InsertListItemPaintingNode(int32_t list_sign, diff --git a/core/renderer/ui_wrapper/painting/android/platform_renderer_context.h b/core/renderer/ui_wrapper/painting/android/platform_renderer_context.h index 067a7d85c5..4c5370b008 100644 --- a/core/renderer/ui_wrapper/painting/android/platform_renderer_context.h +++ b/core/renderer/ui_wrapper/painting/android/platform_renderer_context.h @@ -65,13 +65,15 @@ class PlatformRendererContext { // Register/unregister PlatformRendererAndroid instances void RegisterPlatformRenderer(int32_t id, PlatformRendererAndroid* renderer); void UnregisterPlatformRenderer(int32_t id); - void CreateImage(int32_t id, base::String src, float width, float height, - int32_t event_mask = 0); - void DestroyImage(int32_t id); + void CreateImageResource(int32_t resource_id, int32_t owner_id, + base::String src, float width, float height, + int32_t event_mask = 0); + void DestroyImageResource(int32_t resource_id); - void UpdateTextBundle(int32_t id, intptr_t text_bundle); + void CreateTextResource(int32_t resource_id, int32_t owner_id, + intptr_t text_bundle); - void DestroyTextBundle(int32_t id); + void DestroyTextResource(int32_t resource_id); void InsertListItemPaintingNode(int32_t list_sign, int32_t child_sign); diff --git a/core/renderer/ui_wrapper/painting/ios/native_painting_context_darwin.h b/core/renderer/ui_wrapper/painting/ios/native_painting_context_darwin.h index df8462a1f8..b9ceadcb8b 100644 --- a/core/renderer/ui_wrapper/painting/ios/native_painting_context_darwin.h +++ b/core/renderer/ui_wrapper/painting/ios/native_painting_context_darwin.h @@ -52,7 +52,7 @@ class NativePaintingCtxDarwin : public PaintingCtxPlatformImpl, public NativePai void SetKeyframes(fml::RefPtr keyframes_data) override {} // NativePaintingContextDarwin do not need impl this interface. - virtual void HandleValidate(int tag) override{}; + virtual void HandleValidate(int tag) override {} std::unique_ptr GetTextInfo(const std::string &content, const pub::Value &info) override; @@ -107,9 +107,7 @@ class NativePaintingCtxDarwin : public PaintingCtxPlatformImpl, public NativePai void UpdateDisplayList(int id, DisplayList display_list) override; - void UpdateTextBundle(int id, intptr_t bundle) override; - - void DestroyTextBundle(int id) override; + int32_t CreateTextResource(int owner_id, intptr_t bundle) override; void InsertListItemPaintingNode(int32_t list_id, int32_t child_id) override; @@ -123,8 +121,8 @@ class NativePaintingCtxDarwin : public PaintingCtxPlatformImpl, public NativePai void UpdatePlatformEventBundle(int32_t id, PlatformEventBundle bundle) override; - void CreateImage(int id, base::String src, float width, float height, - int32_t event_mask = 0) override; + int32_t CreateImageResource(int owner_id, base::String src, float width, float height, + int32_t event_mask = 0) override; #pragma endregion // NativePaintingContext diff --git a/core/renderer/ui_wrapper/painting/ios/native_painting_context_darwin.mm b/core/renderer/ui_wrapper/painting/ios/native_painting_context_darwin.mm index e1be4d90ab..52080998d8 100644 --- a/core/renderer/ui_wrapper/painting/ios/native_painting_context_darwin.mm +++ b/core/renderer/ui_wrapper/painting/ios/native_painting_context_darwin.mm @@ -234,23 +234,17 @@ }); } -void NativePaintingCtxDarwin::UpdateTextBundle(int id, intptr_t bundle) { - Enqueue([ref = platform_ref_, id, bundle]() { +int32_t NativePaintingCtxDarwin::CreateTextResource(int owner_id, intptr_t bundle) { + int32_t resource_id = NextPlatformResourceId(); + Enqueue([ref = platform_ref_, resource_id, owner_id, bundle]() { auto darwin_ref = std::static_pointer_cast(ref); if (darwin_ref) { - [darwin_ref->GetRendererContext() updateTextBundle:id - withBundle:reinterpret_cast(bundle)]; - } - }); -} - -void NativePaintingCtxDarwin::DestroyTextBundle(int id) { - Enqueue([ref = platform_ref_, id]() { - auto darwin_ref = std::static_pointer_cast(ref); - if (darwin_ref) { - [darwin_ref->GetRendererContext() destroyTextBundle:id]; + [darwin_ref->GetRendererContext() createTextResource:resource_id + ownerID:owner_id + withBundle:reinterpret_cast(bundle)]; } }); + return resource_id; } void NativePaintingCtxDarwin::InsertListItemPaintingNode(int32_t list_id, int32_t child_id) {} @@ -281,16 +275,19 @@ }); } -void NativePaintingCtxDarwin::CreateImage(int id, base::String src, float width, float height, - int32_t event_mask) { +int32_t NativePaintingCtxDarwin::CreateImageResource(int owner_id, base::String src, float width, + float height, int32_t event_mask) { + int32_t resource_id = NextPlatformResourceId(); LynxURL *sourceUrl = [[LynxURL alloc] init]; sourceUrl.url = [[NSURL alloc] initWithString:[[NSString alloc] initWithUTF8String:src.c_str()]]; sourceUrl.imageSize = CGSizeMake(width, height); - [context_->GetRendererContext() createImageManager:id + [context_->GetRendererContext() createImageManager:resource_id + ownerID:owner_id withSourceURL:sourceUrl andPlaceholderURL:nil eventMask:event_mask]; + return resource_id; } template diff --git a/core/renderer/ui_wrapper/painting/native_painting_context.h b/core/renderer/ui_wrapper/painting/native_painting_context.h index aaeffc48c0..4212f9d279 100644 --- a/core/renderer/ui_wrapper/painting/native_painting_context.h +++ b/core/renderer/ui_wrapper/painting/native_painting_context.h @@ -5,6 +5,7 @@ #ifndef CORE_RENDERER_UI_WRAPPER_PAINTING_NATIVE_PAINTING_CONTEXT_H_ #define CORE_RENDERER_UI_WRAPPER_PAINTING_NATIVE_PAINTING_CONTEXT_H_ +#include #include #include "base/include/value/base_string.h" @@ -14,6 +15,9 @@ namespace lynx::tasm { class DisplayList; class PlatformEventBundle; + +constexpr int32_t kInvalidPlatformResourceId = -1; + class NativePaintingContext { public: NativePaintingContext() = default; @@ -30,10 +34,10 @@ class NativePaintingContext { int id, const base::String& tag_name, const fml::RefPtr& init_data) = 0; virtual void UpdateDisplayList(int id, DisplayList list) = 0; - virtual void CreateImage(int id, base::String src, float width, float height, - int32_t event_mask = 0) = 0; - virtual void UpdateTextBundle(int id, intptr_t bundle) = 0; - virtual void DestroyTextBundle(int id) = 0; + virtual int32_t CreateImageResource(int owner_id, base::String src, + float width, float height, + int32_t event_mask = 0) = 0; + virtual int32_t CreateTextResource(int owner_id, intptr_t bundle) = 0; virtual void InsertListItemPaintingNode(int32_t list_id, int32_t child_id) = 0; virtual void RemoveListItemPaintingNode(int32_t list_id, @@ -46,6 +50,12 @@ class NativePaintingContext { virtual void ReconstructEventTargetTreeRecursively() = 0; virtual void UpdatePlatformEventBundle(int id, PlatformEventBundle bundle) = 0; + + protected: + int32_t NextPlatformResourceId() { return next_platform_resource_id_++; } + + private: + int32_t next_platform_resource_id_{1}; }; } // namespace lynx::tasm diff --git a/platform/android/api/lynx_android.api b/platform/android/api/lynx_android.api index fe98d02fba..bd00dee76f 100644 --- a/platform/android/api/lynx_android.api +++ b/platform/android/api/lynx_android.api @@ -8785,10 +8785,10 @@ public class com::lynx::tasm::behavior::render::PlatformRendererContext : com.ly public void com.lynx.tasm.behavior.render.PlatformRendererContext.updatePlatformRendererSubtreeProperties(int sign, ByteBuffer buffer, int count); public void com.lynx.tasm.behavior.render.PlatformRendererContext.updatePlatformExtraData(int sign, Object extraData); public LynxImageManager com.lynx.tasm.behavior.render.PlatformRendererContext.getImage(int sign); - public void com.lynx.tasm.behavior.render.PlatformRendererContext.createImage(int sign, String src, int width, int height, int eventMask); - public void com.lynx.tasm.behavior.render.PlatformRendererContext.destroyImage(int sign); - public void com.lynx.tasm.behavior.render.PlatformRendererContext.updateTextBundle(int sign, long textBundle); - public void com.lynx.tasm.behavior.render.PlatformRendererContext.destroyTextBundle(final int sign); + public void com.lynx.tasm.behavior.render.PlatformRendererContext.createImage(int resourceId, int ownerSign, String src, int width, int height, int eventMask); + public void com.lynx.tasm.behavior.render.PlatformRendererContext.destroyImage(int resourceId); + public void com.lynx.tasm.behavior.render.PlatformRendererContext.updateTextBundle(int resourceId, int ownerSign, long textBundle); + public void com.lynx.tasm.behavior.render.PlatformRendererContext.destroyTextBundle(final int resourceId); public void com.lynx.tasm.behavior.render.PlatformRendererContext.insertListItemPaintingNode(int listSign, int childSign); public void com.lynx.tasm.behavior.render.PlatformRendererContext.removeListItemPaintingNode(int listSign, int childSign); public void com.lynx.tasm.behavior.render.PlatformRendererContext.updateContentOffsetForListContainer(int listSign, float contentSize, float deltaX, float deltaY, boolean isInitScrollOffset, boolean fromLayout); diff --git a/platform/android/lynx_android/src/android_test/java/com/lynx/tasm/behavior/render/DisplayListApplierTest.java b/platform/android/lynx_android/src/android_test/java/com/lynx/tasm/behavior/render/DisplayListApplierTest.java index ea7f36f4e0..e0fb5cf681 100644 --- a/platform/android/lynx_android/src/android_test/java/com/lynx/tasm/behavior/render/DisplayListApplierTest.java +++ b/platform/android/lynx_android/src/android_test/java/com/lynx/tasm/behavior/render/DisplayListApplierTest.java @@ -108,6 +108,8 @@ public void setUp() { MockitoAnnotations.openMocks(this); // Set up PlatformRendererContext to return our mock TextMeasurer when(mockPlatformRendererContext.getTextMeasurer()).thenReturn(mockTextMeasurer); + when(mockPlatformRendererContext.getTextLayoutSign(anyInt())) + .thenAnswer(invocation -> invocation.getArgument(0)); when(mockRendererHost.getView()).thenReturn(mockHostView); displayListApplier = new DisplayListApplier(null, mockPlatformRendererContext, mockRendererHost); @@ -573,6 +575,28 @@ public void testMultipleOperations() { *
  • Canvas.restore() is called for OP_END
  • * */ + @Test + public void testSetDisplayListReleasesUnusedTextAndImageResources() { + DisplayList listWithResources = new DisplayList(); + listWithResources.ops = new int[] {6, 7}; // OP_TEXT, OP_IMAGE + listWithResources.iArgv = new int[] { + 2, 0, 101, -1, // OP_TEXT: resource id, box index + 2, 0, 202, -1 // OP_IMAGE: resource id, box index + }; + listWithResources.fArgv = new float[] {}; + + DisplayList emptyList = new DisplayList(); + emptyList.ops = new int[] {}; + emptyList.iArgv = new int[] {}; + emptyList.fArgv = new float[] {}; + + displayListApplier.setDisplayList(listWithResources); + displayListApplier.setDisplayList(emptyList); + + verify(mockPlatformRendererContext).releaseTextResource(101); + verify(mockPlatformRendererContext).releaseImageResource(202); + } + @Test public void testOpBorderUniform() { testDisplayList.ops = diff --git a/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/render/DisplayListApplier.java b/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/render/DisplayListApplier.java index 3be83b69bd..4bbe79726c 100644 --- a/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/render/DisplayListApplier.java +++ b/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/render/DisplayListApplier.java @@ -31,6 +31,8 @@ import com.lynx.tasm.service.ILynxTextService.Page; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; import java.util.Stack; public class DisplayListApplier implements Drawable.Callback { @@ -57,6 +59,8 @@ public class DisplayListApplier implements Drawable.Callback { private DisplayList mDisplayList; private TextMeasurer mTextMeasurer; private Paint mPaint; + private Set mTextResourceRefs = new HashSet<>(); + private Set mImageResourceRefs = new HashSet<>(); // Reusable objects for optimization private final Path mReusablePath = new Path(); @@ -93,6 +97,8 @@ public DisplayListApplier(DisplayList displayList, mTextMeasurer = platformRendererContext.getTextMeasurer(); mContext = platformRendererContext; mHostLayer = new WeakReference<>(hostLayer); + mTextResourceRefs = collectResourceRefs(displayList, OP_TEXT); + mImageResourceRefs = collectResourceRefs(displayList, OP_IMAGE); // The drawing position on Android is affected by the frame layout and the // frame in OP_BEGIN togather. For a indepent layer, its position is already @@ -294,7 +300,8 @@ private void drawText(Canvas canvas, int textId) { if (mTextMeasurer == null) { return; } - TextUpdateBundle textBundle = (TextUpdateBundle) mTextMeasurer.takeTextLayout(textId); + TextUpdateBundle textBundle = + (TextUpdateBundle) mTextMeasurer.takeTextLayout(mContext.getTextLayoutSign(textId)); if (textBundle == null) { return; } @@ -842,10 +849,69 @@ public void setDisplayList(DisplayList displayList) { if (displayList != null && displayList.fArgv != null && displayList.fArgv.length >= 2) { displayList.fArgv[0] = displayList.fArgv[1] = 0.f; } + updateResourceRefs(displayList); mDisplayList = displayList; reset(); } + private void updateResourceRefs(DisplayList displayList) { + Set newTextResourceRefs = collectResourceRefs(displayList, OP_TEXT); + Set newImageResourceRefs = collectResourceRefs(displayList, OP_IMAGE); + releaseResourceRefsExcept(newTextResourceRefs, newImageResourceRefs); + mTextResourceRefs = newTextResourceRefs; + mImageResourceRefs = newImageResourceRefs; + } + + void releaseAllResources() { + releaseResourceRefsExcept(new HashSet<>(), new HashSet<>()); + mTextResourceRefs.clear(); + mImageResourceRefs.clear(); + } + + private void releaseResourceRefsExcept( + Set newTextResourceRefs, Set newImageResourceRefs) { + if (mContext != null) { + for (Integer textResourceId : mTextResourceRefs) { + if (!newTextResourceRefs.contains(textResourceId)) { + mContext.releaseTextResource(textResourceId); + } + } + for (Integer imageResourceId : mImageResourceRefs) { + if (!newImageResourceRefs.contains(imageResourceId)) { + mContext.releaseImageResource(imageResourceId); + } + } + } + } + + private Set collectResourceRefs(DisplayList displayList, int targetOp) { + Set resourceRefs = new HashSet<>(); + if (displayList == null || displayList.ops == null || displayList.iArgv == null) { + return resourceRefs; + } + + int intIndex = 0; + int floatIndex = 0; + for (int op : displayList.ops) { + if (intIndex + 2 > displayList.iArgv.length) { + break; + } + int intParamCount = displayList.iArgv[intIndex++]; + int floatParamCount = displayList.iArgv[intIndex++]; + int paramsStart = intIndex; + if (op == targetOp && intParamCount >= 1 && paramsStart < displayList.iArgv.length) { + resourceRefs.add(displayList.iArgv[paramsStart]); + } + intIndex += Math.max(intParamCount, 0); + floatIndex += Math.max(floatParamCount, 0); + if (intIndex > displayList.iArgv.length + || (displayList.fArgv != null && floatIndex > displayList.fArgv.length)) { + break; + } + } + return resourceRefs; + } + @Override public void invalidateDrawable(@NonNull Drawable who) { IRendererHost hostLayer = getRendererHost(); diff --git a/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/render/PlatformRendererContext.java b/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/render/PlatformRendererContext.java index b5a065c3ba..636b45b1ff 100644 --- a/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/render/PlatformRendererContext.java +++ b/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/render/PlatformRendererContext.java @@ -80,6 +80,10 @@ public static final class PlatformEventHandlerState { private boolean mDestroyed = false; private ConcurrentHashMap mExtraDatas = new ConcurrentHashMap<>(); + private ConcurrentHashMap mImageResources = new ConcurrentHashMap<>(); + private ConcurrentHashMap mOwnerImageResources = new ConcurrentHashMap<>(); + private ConcurrentHashMap mOwnerTextResources = new ConcurrentHashMap<>(); + private ConcurrentHashMap mTextResourceOwners = new ConcurrentHashMap<>(); // TextMeasurer instance for text measurement functionality private TextMeasurer mTextMeasurer = null; @@ -587,7 +591,18 @@ public void updatePlatformExtraData(int sign, Object extraData) { } public LynxImageManager getImage(int sign) { - UIBody.UIBodyView rootView = mRootView.get(); + LynxImageManager imageManager = mImageResources.get(sign); + if (imageManager != null) { + return imageManager; + } + Integer resourceId = mOwnerImageResources.get(sign); + if (resourceId != null) { + imageManager = mImageResources.get(resourceId); + if (imageManager != null) { + return imageManager; + } + } + UIBody.UIBodyView rootView = mRootView != null ? mRootView.get() : null; if (rootView != null) { return rootView.peekImageAccordingToNodeIndex(sign); } @@ -595,49 +610,84 @@ public LynxImageManager getImage(int sign) { } @CalledByNative - public void createImage(int sign, String src, int width, int height, int eventMask) { - // Create Image managed by LynxImageManager and register to UIBodyView + public void createImage( + int resourceId, int ownerSign, String src, int width, int height, int eventMask) { LynxImageManager imageManager = new LynxImageManager(mContext); - imageManager.setFallbackSign(sign); + imageManager.setFallbackSign(ownerSign); imageManager.setEventMask(eventMask); imageManager.setSrc(src); imageManager.onLayoutUpdated(width, height, 0, 0, 0, 0); - UIBody.UIBodyView rootView = mRootView.get(); - if (rootView != null) { - rootView.registerImageAccordingToNodeIndex(sign, imageManager); - } + mImageResources.put(resourceId, imageManager); + mOwnerImageResources.put(ownerSign, resourceId); imageManager.onNodeReady(); } @CalledByNative - public void destroyImage(int sign) { - // Remove and release the image source from UIBodyView - UIBody.UIBodyView rootView = mRootView.get(); - if (rootView != null) { - rootView.obtainImageAccordingToNodeIndex(sign); + public void destroyImage(int resourceId) { + releaseImageResource(resourceId); + } + + void releaseImageResource(int resourceId) { + LynxImageManager imageManager = mImageResources.remove(resourceId); + if (imageManager == null) { + return; + } + imageManager.destroy(); + Integer ownerSignToRemove = null; + for (Map.Entry entry : mOwnerImageResources.entrySet()) { + if (entry.getValue() == resourceId) { + ownerSignToRemove = entry.getKey(); + break; + } + } + if (ownerSignToRemove != null) { + mOwnerImageResources.remove(ownerSignToRemove); } } Page getTextBundle(int sign) { - return (Page) mExtraDatas.get(sign); + Object page = mExtraDatas.get(sign); + if (page instanceof Page) { + return (Page) page; + } + Integer resourceId = mOwnerTextResources.get(sign); + if (resourceId == null) { + return null; + } + page = mExtraDatas.get(resourceId); + return page instanceof Page ? (Page) page : null; } @CalledByNative - public void updateTextBundle(int sign, long textBundle) { - // Update the text layout bundle for the specified sign + public void updateTextBundle(int resourceId, int ownerSign, long textBundle) { + mTextResourceOwners.put(resourceId, ownerSign); + mOwnerTextResources.put(ownerSign, resourceId); + if (mContext == null || !mContext.isTextServiceModeOn()) { + return; + } Page page = mContext.getTextService().createPage(textBundle); if (page != null) { - mExtraDatas.put(sign, page); + mExtraDatas.put(resourceId, page); } } @CalledByNative - public void destroyTextBundle(final int sign) { - // Destroy the text layout bundle for the specified sign + public void destroyTextBundle(final int resourceId) { + releaseTextResource(resourceId); + } + + void releaseTextResource(final int resourceId) { UIThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { - Page page = (Page) mExtraDatas.remove(sign); + Integer ownerSign = mTextResourceOwners.remove(resourceId); + if (ownerSign != null) { + Integer ownerResourceId = mOwnerTextResources.get(ownerSign); + if (ownerResourceId != null && ownerResourceId == resourceId) { + mOwnerTextResources.remove(ownerSign); + } + } + Page page = (Page) mExtraDatas.remove(resourceId); if (page != null) { page.destroy(); } @@ -645,6 +695,11 @@ public void run() { }); } + int getTextLayoutSign(int textResourceId) { + Integer ownerSign = mTextResourceOwners.get(textResourceId); + return ownerSign != null ? ownerSign : textResourceId; + } + @CalledByNative public void insertListItemPaintingNode(int listSign, int childSign) { LynxUIOwner owner = mContext != null ? mContext.getLynxUIOwner() : null; @@ -822,10 +877,17 @@ public void destroy() { } } mExtraDatas.clear(); + for (LynxImageManager imageManager : mImageResources.values()) { + imageManager.destroy(); + } + mImageResources.clear(); + mOwnerImageResources.clear(); + mOwnerTextResources.clear(); + mTextResourceOwners.clear(); mTextMeasurer = null; mTextLayout = null; - UIBody.UIBodyView root = mRootView.get(); + UIBody.UIBodyView root = mRootView != null ? mRootView.get() : null; if (root != null) { root.clearNodeIndexImageMap(); } diff --git a/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/render/Renderer.java b/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/render/Renderer.java index 21119a3552..2888bc068b 100644 --- a/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/render/Renderer.java +++ b/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/render/Renderer.java @@ -216,7 +216,12 @@ public void invalidate(int invalidateMask) { public void updateAttributes(PropBundle props) {} public void updateExtraData(Object extraData) {} - public void onDestroy() {} + public void onDestroy() { + if (mDisplayListApplier != null) { + mDisplayListApplier.releaseAllResources(); + mDisplayListApplier = null; + } + } public void applySubtreeProperties(java.nio.ByteBuffer buffer, int count) { IRendererHost rendererHost = getRendererHost(); diff --git a/platform/darwin/ios/api/lynx_ios.api b/platform/darwin/ios/api/lynx_ios.api index 7f2fb6b2b6..05922b4f8f 100644 --- a/platform/darwin/ios/api/lynx_ios.api +++ b/platform/darwin/ios/api/lynx_ios.api @@ -3315,10 +3315,11 @@ public class LynxRendererContext : NSObject { public UIView* LynxRendererContext::bodyView bodyView; public LynxUIContext* LynxRendererContext::uiContext uiContext; public LynxTextRenderManager* LynxRendererContext::textRenderManager textRenderManager; - public void LynxRendererContext::createImageManager:withSourceURL:andPlaceholderURL:eventMask:(int32_t imageManagerID,[withSourceURL] LynxURL *sourceURL,[andPlaceholderURL] LynxURL *placeholderURL,[eventMask] int32_t eventMask); - public LynxImageManager * LynxRendererContext::takeImageManager:(int32_t imageManagerID); - public void LynxRendererContext::updateTextBundle:withBundle:(int32_t textID,[withBundle] void *bundle); - public void LynxRendererContext::destroyTextBundle:(int32_t textID); + public void LynxRendererContext::createImageManager:ownerID:withSourceURL:andPlaceholderURL:eventMask:(int32_t imageManagerID,[ownerID] int32_t ownerID,[withSourceURL] LynxURL *sourceURL,[andPlaceholderURL] LynxURL *placeholderURL,[eventMask] int32_t eventMask); + public LynxImageManager * LynxRendererContext::getImageManager:(int32_t imageManagerID); + public void LynxRendererContext::releaseImageManager:(int32_t imageManagerID); + public void LynxRendererContext::createTextResource:ownerID:withBundle:(int32_t textID,[ownerID] int32_t ownerID,[withBundle] void *bundle); + public void LynxRendererContext::releaseTextResource:(int32_t textID); public void * LynxRendererContext::getTextBundle:(int32_t textID); } diff --git a/platform/darwin/ios/lynx/renderer/LynxDisplayListApplier.mm b/platform/darwin/ios/lynx/renderer/LynxDisplayListApplier.mm index c30d681e81..aaada20661 100644 --- a/platform/darwin/ios/lynx/renderer/LynxDisplayListApplier.mm +++ b/platform/darwin/ios/lynx/renderer/LynxDisplayListApplier.mm @@ -13,18 +13,57 @@ #import "LynxDisplayListApplier+Internal.h" #import "LynxTextraLayer.h" +#include #include +#include #include "base/include/vector.h" #include "core/renderer/dom/fragment/rounded_rectangle.h" using namespace lynx; using namespace lynx::tasm; +namespace { + +std::unordered_set CollectResourceRefs(DisplayList *list, DisplayListOpType target_op) { + std::unordered_set refs; + if (list == nullptr || list->GetContentOpTypesData() == nullptr || + list->GetContentIntData() == nullptr) { + return refs; + } + + const int32_t *ops = list->GetContentOpTypesData(); + const int32_t *ints = list->GetContentIntData(); + size_t int_index = 0; + size_t float_index = 0; + for (size_t op_index = 0; op_index < list->GetContentOpTypesSize(); ++op_index) { + if (int_index + 2 > list->GetContentIntDataSize()) { + break; + } + int32_t int_count = ints[int_index++]; + int32_t float_count = ints[int_index++]; + size_t params_start = int_index; + if (static_cast(ops[op_index]) == target_op && int_count >= 1 && + params_start < list->GetContentIntDataSize()) { + refs.insert(ints[params_start]); + } + int_index += std::max(int_count, 0); + float_index += std::max(float_count, 0); + if (int_index > list->GetContentIntDataSize() || + float_index > list->GetContentFloatDataSize()) { + break; + } + } + return refs; +} + +} // namespace + @interface LynxDisplayListApplier () - (void)insertHostDecorationLayer:(CALayer *)layer; - (void)insertLayer:(CALayer *)layer forOp:(DisplayListOpType)op; - (BOOL)shouldInsertAsHostDecorationForOp:(DisplayListOpType)op; +- (void)releaseCurrentResourceRefs; - (void)applyRoundedRect:(const RoundedRectangle &)box toLayer:(CALayer *)layer; - (CGRect)rectForRoundedRectangle:(const RoundedRectangle &)rect applyingOffsets:(BOOL)applyOffsets; - (CALayer *)createLinearGradientLayerWithAngle:(float)angle @@ -68,6 +107,8 @@ @implementation LynxDisplayListApplier { NSMutableArray *_contentImageViews; NSMutableArray *_contentLayers; NSMutableArray *_hostDecorationLayers; + std::unordered_set text_resource_refs_; + std::unordered_set image_resource_refs_; } - (instancetype)initWithView:(UIView *)view @@ -93,6 +134,22 @@ - (instancetype)initWithView:(UIView *)view return self; } +- (void)dealloc { + [self releaseCurrentResourceRefs]; +} + +- (void)releaseCurrentResourceRefs { + for (int32_t textID : text_resource_refs_) { + [_renderer_context releaseTextResource:textID]; + } + for (int32_t imageID : image_resource_refs_) { + [_renderer_context releaseImageManager:imageID]; + } + text_resource_refs_.clear(); + image_resource_refs_.clear(); + [_imageManagers removeAllObjects]; +} + - (int32_t)nextContentInt { return list_->GetIntAtIndex(content_int_index_++); } @@ -218,6 +275,9 @@ - (void)processContentOperations { auto image_id = [self nextContentInt]; [[maybe_unused]] auto box_index = [self nextContentInt]; LynxImageManager *imageManager = [self imageManagerForID:image_id]; + if (imageManager == nil) { + break; + } UIImageView *imageView = [self createImageView]; @@ -426,6 +486,9 @@ - (void)processContentOperations { - (void)applyDisplayList:(lynx::tasm::DisplayList *)list { if (list == nullptr) { + [self releaseCurrentResourceRefs]; + [self reset]; + list_ = nullptr; return; } @@ -433,6 +496,22 @@ - (void)applyDisplayList:(lynx::tasm::DisplayList *)list { return; } + std::unordered_set newTextRefs = CollectResourceRefs(list, DisplayListOpType::kText); + std::unordered_set newImageRefs = CollectResourceRefs(list, DisplayListOpType::kImage); + for (int32_t textID : text_resource_refs_) { + if (newTextRefs.find(textID) == newTextRefs.end()) { + [_renderer_context releaseTextResource:textID]; + } + } + for (int32_t imageID : image_resource_refs_) { + if (newImageRefs.find(imageID) == newImageRefs.end()) { + [_imageManagers removeObjectForKey:@(imageID)]; + [_renderer_context releaseImageManager:imageID]; + } + } + text_resource_refs_ = std::move(newTextRefs); + image_resource_refs_ = std::move(newImageRefs); + [self reset]; list_ = list; @@ -715,7 +794,7 @@ - (LynxImageManager *)imageManagerForID:(int32_t)imageManagerID { } LynxImageManager *imageManager = _imageManagers[@(imageManagerID)]; if (imageManager == nil) { - imageManager = [_renderer_context takeImageManager:imageManagerID]; + imageManager = [_renderer_context getImageManager:imageManagerID]; if (imageManager != nil) { _imageManagers[@(imageManagerID)] = imageManager; } diff --git a/platform/darwin/ios/lynx/renderer/LynxDisplayListApplierUnitTest.mm b/platform/darwin/ios/lynx/renderer/LynxDisplayListApplierUnitTest.mm index 67d2fca714..3f04090e07 100644 --- a/platform/darwin/ios/lynx/renderer/LynxDisplayListApplierUnitTest.mm +++ b/platform/darwin/ios/lynx/renderer/LynxDisplayListApplierUnitTest.mm @@ -173,6 +173,36 @@ - (void)testProcessContentOperationsWithTODOs { // Verify no crash } +- (void)testApplyDisplayListReleasesUnusedTextAndImageResources { + id mockUIView = OCMClassMock([LynxMockView class]); + id mockLayer = OCMClassMock([CALayer class]); + id mockContext = OCMClassMock([LynxRendererContext class]); + [[[mockUIView stub] andReturn:mockLayer] layer]; + + LynxDisplayListApplier *applier = [[LynxDisplayListApplier alloc] initWithView:mockUIView + andContext:mockContext]; + + DisplayList list1; + list1.AddOperation(DisplayListOpType::kRecordBox, 0.0f, 0.0f, 10.0f, 10.0f); + list1.AddOperation(DisplayListOpType::kText, 101, 0); + list1.AddOperation(DisplayListOpType::kImage, 202, 0); + [applier applyDisplayList:&list1]; + + [[mockContext expect] releaseTextResource:101]; + [[mockContext expect] releaseImageManager:202]; + DisplayList list2; + list2.AddOperation(DisplayListOpType::kRecordBox, 0.0f, 0.0f, 10.0f, 10.0f); + list2.AddOperation(DisplayListOpType::kText, 303, 0); + list2.AddOperation(DisplayListOpType::kImage, 404, 0); + [applier applyDisplayList:&list2]; + [mockContext verify]; + + [[mockContext expect] releaseTextResource:303]; + [[mockContext expect] releaseImageManager:404]; + [applier applyDisplayList:nullptr]; + [mockContext verify]; +} + - (void)testDrawViewWithIntCountNotOne { id mockUIView = OCMClassMock([LynxMockView class]); id mockSubView = OCMClassMock([UIView class]); diff --git a/platform/darwin/ios/lynx/renderer/LynxRendererContext.h b/platform/darwin/ios/lynx/renderer/LynxRendererContext.h index 1c3f9f6518..a4c7b0142f 100644 --- a/platform/darwin/ios/lynx/renderer/LynxRendererContext.h +++ b/platform/darwin/ios/lynx/renderer/LynxRendererContext.h @@ -15,15 +15,18 @@ @property(nonatomic, strong) LynxTextRenderManager *textRenderManager; - (void)createImageManager:(int32_t)imageManagerID + ownerID:(int32_t)ownerID withSourceURL:(LynxURL *)sourceURL andPlaceholderURL:(LynxURL *)placeholderURL eventMask:(int32_t)eventMask; -- (LynxImageManager *)takeImageManager:(int32_t)imageManagerID; +- (LynxImageManager *)getImageManager:(int32_t)imageManagerID; -- (void)updateTextBundle:(int32_t)textID withBundle:(void *)bundle; +- (void)releaseImageManager:(int32_t)imageManagerID; -- (void)destroyTextBundle:(int32_t)textID; +- (void)createTextResource:(int32_t)textID ownerID:(int32_t)ownerID withBundle:(void *)bundle; + +- (void)releaseTextResource:(int32_t)textID; - (void *)getTextBundle:(int32_t)textID; diff --git a/platform/darwin/ios/lynx/renderer/LynxRendererContext.mm b/platform/darwin/ios/lynx/renderer/LynxRendererContext.mm index dadfe1ad83..1b70de36c7 100644 --- a/platform/darwin/ios/lynx/renderer/LynxRendererContext.mm +++ b/platform/darwin/ios/lynx/renderer/LynxRendererContext.mm @@ -22,69 +22,94 @@ void DestroyTextBundlePointer(void *bundle) { @implementation LynxRendererContext { NSMutableDictionary *_imageManagers; + NSMutableDictionary *_ownerImageResources; + NSMutableDictionary *_imageResourceOwners; NSMutableDictionary *_textBundles; + NSMutableDictionary *_ownerTextResources; + NSMutableDictionary *_textResourceOwners; } - (instancetype)init { self = [super init]; if (self) { _imageManagers = [NSMutableDictionary new]; + _ownerImageResources = [NSMutableDictionary new]; + _imageResourceOwners = [NSMutableDictionary new]; _textBundles = [NSMutableDictionary new]; + _ownerTextResources = [NSMutableDictionary new]; + _textResourceOwners = [NSMutableDictionary new]; } return self; } - (void)dealloc { + for (LynxImageManager *imageManager in _imageManagers.objectEnumerator) { + [imageManager reset]; + } for (NSValue *value in _textBundles.objectEnumerator) { DestroyTextBundlePointer([value pointerValue]); } } - (void)createImageManager:(int32_t)imageManagerID + ownerID:(int32_t)ownerID withSourceURL:(LynxURL *)sourceURL andPlaceholderURL:(LynxURL *)placeholderURL eventMask:(int32_t)eventMask { LynxImageManager *imageManager = [[LynxImageManager alloc] initWithContext:_uiContext]; - [imageManager setSign:imageManagerID]; + [imageManager setSign:ownerID]; [imageManager setEventMask:eventMask]; [imageManager requestImage:sourceURL withType:LynxImageRequestSrc]; [imageManager requestImage:sourceURL withType:LynxImageRequestPlaceholder]; @synchronized(self) { _imageManagers[@(imageManagerID)] = imageManager; + _imageResourceOwners[@(imageManagerID)] = @(ownerID); + _ownerImageResources[@(ownerID)] = @(imageManagerID); } } -- (LynxImageManager *)takeImageManager:(int32_t)imageManagerID { +- (LynxImageManager *)getImageManager:(int32_t)imageManagerID { LynxImageManager *imageManager = nil; @synchronized(self) { imageManager = _imageManagers[@(imageManagerID)]; - [_imageManagers removeObjectForKey:@(imageManagerID)]; } return imageManager; } -- (void)updateTextBundle:(int32_t)textID withBundle:(void *)bundle { - void *previousBundle = nullptr; +- (void)releaseImageManager:(int32_t)imageManagerID { @synchronized(self) { - NSValue *previousValue = _textBundles[@(textID)]; - if (previousValue != nil) { - previousBundle = [previousValue pointerValue]; - } - if (previousBundle == bundle) { - return; + NSNumber *key = @(imageManagerID); + NSNumber *ownerID = _imageResourceOwners[key]; + if (ownerID != nil && [_ownerImageResources[ownerID] isEqualToNumber:key]) { + [_ownerImageResources removeObjectForKey:ownerID]; } + [_imageManagers[key] reset]; + [_imageResourceOwners removeObjectForKey:key]; + [_imageManagers removeObjectForKey:key]; + } +} + +- (void)createTextResource:(int32_t)textID ownerID:(int32_t)ownerID withBundle:(void *)bundle { + @synchronized(self) { + _textResourceOwners[@(textID)] = @(ownerID); + _ownerTextResources[@(ownerID)] = @(textID); _textBundles[@(textID)] = [NSValue valueWithPointer:bundle]; } - DestroyTextBundlePointer(previousBundle); } -- (void)destroyTextBundle:(int32_t)textID { +- (void)releaseTextResource:(int32_t)textID { void *bundle = nullptr; @synchronized(self) { - NSValue *value = _textBundles[@(textID)]; - if (value) { + NSNumber *key = @(textID); + NSNumber *ownerID = _textResourceOwners[key]; + if (ownerID != nil && [_ownerTextResources[ownerID] isEqualToNumber:key]) { + [_ownerTextResources removeObjectForKey:ownerID]; + } + [_textResourceOwners removeObjectForKey:key]; + NSValue *value = _textBundles[key]; + if (value != nil) { bundle = [value pointerValue]; - [_textBundles removeObjectForKey:@(textID)]; + [_textBundles removeObjectForKey:key]; } } DestroyTextBundlePointer(bundle); @@ -93,7 +118,12 @@ - (void)destroyTextBundle:(int32_t)textID { - (void *)getTextBundle:(int32_t)textID { void *bundle = nullptr; @synchronized(self) { - NSValue *value = _textBundles[@(textID)]; + NSNumber *key = @(textID); + NSValue *value = _textBundles[key]; + if (value == nil) { + NSNumber *resourceID = _ownerTextResources[key]; + value = resourceID != nil ? _textBundles[resourceID] : nil; + } if (value) { bundle = [value pointerValue]; }