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]; }